if (R.version$major < 4) {
  warning("Not using R 4.0. It should.")
}
if (!require("pacman")) install.packages("pacman")
Loading required package: pacman
Warning: package ‘pacman’ was built under R version 4.3.2
pacman::p_load(
  tidyverse, # 
  conflicted,
  lubridate,
  simmer,
  simmer.plot,
  svglite
)
library(tidyverse)
library(conflicted)
library(lubridate)
conflicted::conflict_prefer("filter", "dplyr", c("base", "stats"))
[conflicted] Removing existing preference.[conflicted] Will prefer dplyr::filter over base::filter and stats::filter.
library(simmer)
library(simmer.plot)
conflicts_prefer(simmer::rollback)
[conflicted] Will prefer simmer::rollback over any other package.
conflicts_prefer(simmer::now)
[conflicted] Will prefer simmer::now over any other package.
conflicts_prefer(dplyr::select)
[conflicted] Will prefer dplyr::select over any other package.
options(
  repos=c(CRAN="https://cran.radicaldevelop.com/"),
  tidyverse.quiet = TRUE
)
# this will be substituted below and literally just is here because simmer doesn't know how to implement pipes and I hate it here

env <- simmer("hospital") 

Processo

Um paciente que está em lista de espera para ser operado:

  1. Entra em contacto com o serviço de cirurgia, vários dias antes da sua operação,
  1. Os serviços administrativos atendem as chamadas ou lêem a mensagem via app e, mediante

Contudo, este procedimento necessita de melhorias. O objectivo deste estudo é optimizar a gestão dos contactos (tempo total do processo e total de chamadas perdidas)

Situação atual

  1. Apenas 2 administrativos no serviço, ambos começam às 8h e acabam às 18h (10h de trabalho) de cada dia útil.

  2. O tempo entre chegadas tem exp(1/40) (pq media de 40/h)

  3. Tipo de contacto:

  1. Tipo de consulta:
  1. Tempo de
  1. 5o telefone desliga apos 5 minutos de espera (e fica chamada perdida)
library(simmer)
library(simmer.bricks)
Warning: package ‘simmer.bricks’ was built under R version 4.3.2
library(simmer.plot)
conflicts_prefer(simmer::rollback)
[conflicted] Removing existing preference.[conflicted] Will prefer simmer::rollback over any other package.
conflicts_prefer(simmer::now)
[conflicted] Removing existing preference.[conflicted] Will prefer simmer::now over any other package.
conflicts_prefer(dplyr::select)
[conflicted] Removing existing preference.[conflicted] Will prefer dplyr::select over any other package.
# this will be substituted below and literally just is here because simmer doesn't know how to implement pipes and I hate it here
env <- simmer("hospital") 

Resources: Server: O hospital com 2 administradores Queue: Fila de espera com prioridade de clients em chamada Não há vantgem em escolher a app quando há chamada de espera Manager: vai atribuir o tipo de consulta (e Vai alterar o tipo de cliente se ele for rejeitado na chamada?) Gerador: vai gerir novos arrivals (exp(1/40)) arrival: cada paciente (que leva uma trajectory) trajectory: a receita de cada arrival

# atributes need to be numeric
tipo_de_consulta.PRESENCIAL <- 1
tipo_de_consulta.TELEFONICA <- 2
tipo_de_consulta.NAO_AGENDADA <- 3
tipos_de_consulta <- c(tipo_de_consulta.PRESENCIAL, tipo_de_consulta.TELEFONICA, tipo_de_consulta.NAO_AGENDADA)
tipos_de_consulta.probs <- c(0.1, 0.6, 0.3)

tipo_de_contacto.TELEFONE <- 1
tipo_de_contacto.APP <- 2
tipos_de_contacto <- c(tipo_de_contacto.TELEFONE, tipo_de_contacto.APP)
tipos_de_contacto.probs <- c(0.85, 0.15)

tempo_atendido <- function() {
  mean_time_triagem <- rnorm(1, mean = 4, sd = 1)
  mean_time_decision <- rnorm(1, mean = 1, sd = 0.25)
  time_to_timeout <- mean_time_triagem + mean_time_decision
  
  if (get_attribute(env, "tipo_de_consulta") != tipo_de_consulta.NAO_AGENDADA) {
    time_to_timeout <- time_to_timeout + rnorm(1, mean = 1, sd = 0.25)
  }
  
  return(time_to_timeout)
}

intime <- function() {
  now(env)%% 24*60 -> n
  return(8*60 < n & n < 18*60)
}
pessoa <- trajectory("pessoa") %>%
  # set_attribute("times_phone_rejected", 0) %>% 
  # set_attribute("phone_rejected", 0) %>%
  set_attribute("tipo_de_consulta", \() sample(tipos_de_consulta, 1, prob = tipos_de_consulta.probs)) %>%
  set_attribute("tipo_de_contacto", \() sample(tipos_de_contacto, 1, prob = tipos_de_contacto.probs), tag = "contacto") %>%
  set_prioritization(\() if (get_attribute(env, "tipo_de_contacto") == tipo_de_contacto.TELEFONE) c(1, -1, FALSE) else c(0 , -1, FALSE)) %>% # PRIORITY, DROP PRIORITY, RESTART
  # log_(\() paste("Vou pra fila agora, estou a usar", ifelse(get_attribute(env, "tipo_de_contacto") == tipo_de_contacto.TELEFONE, "o telefone", "a app"))) %>%
  renege_in(\() ifelse(get_attribute(env, "tipo_de_contacto") == tipo_de_contacto.TELEFONE, 5, Inf), out = 
              trajectory() %>% set_attribute("phone_rejected", 1)
              # O botas não gostou
              # trajectory("pessoa_try_again") %>% 
              # set_attribute("times_phone_rejected", \() get_attribute(env, "times_phone_rejected") + 1) %>% 
              # # leave if out of schedule
              # leave(\() {ifelse(intime(), 0, 1)}) %>% 
              # # log_(\() "Nao fui atendido, vou voltar pra fila") %>% 
              # rollback(target="contacto", times = 1) # não funciona, mudar pra numero?
            ) %>%
  seize("administrador", 1) %>%
  renege_abort() %>%
  # log_(\() paste("Before timeout")) %>% 
  set_attribute("atendido", 1) %>% 
  timeout(tempo_atendido) %>% 
  # log_(\() "After timeout") %>% 
  release("administrador", 1)
  # log_(\() "Leaving")

pessoa
trajectory: pessoa, 10 activities
{ Activity: SetAttribute | keys: [tipo_de_consulta], values: function(), global: 0, mod: N, init: 0 }
{ Activity: SetAttribute | [contacto] keys: [tipo_de_contacto], values: function(), global: 0, mod: N, init: 0 }
{ Activity: SetPrior     | values: function(), mod: N }
{ Activity: RenegeIn     | t: function(), keep_seized: 0 }
  Fork 1, stop,  trajectory: anonymous, 1 activities
  { Activity: SetAttribute | keys: [phone_rejected], values: [1], global: 0, mod: N, init: 0 }
{ Activity: Seize        | resource: administrador, amount: 1 }
{ Activity: RenegeAbort  |  }
{ Activity: SetAttribute | keys: [atendido], values: [1], global: 0, mod: N, init: 0 }
{ Activity: Timeout      | delay: function() }
{ Activity: Release      | resource: administrador, amount: 1 }
set.seed(1)
# 50 replicas
envs <- lapply(1:50, function(i) {
  env <<- simmer("hospital") 
  env %>% 
    add_generator("pessoa", pessoa, from_to(8*60, 18*60, \() rexp(1, 40/60), every = 24*60, arrive = F), mon = 2) %>% 
    add_resource("administrador", schedule(c(8*60, 18*60), c(2, 0), period = 24*60)) %>%
    run(24*60*5)
})

envs %>% simmer.plot::get_mon_arrivals(ongoing = T) -> arrivals 
envs %>% simmer.plot::get_mon_attributes() -> attributes
envs %>% simmer.plot::get_mon_resources() -> resources

arrivals %>%
  filter(start_time != -1) %>% # n sei pq e q isto acontece
  arrange(start_time) %>% 
  select(-activity_time) %>% 
  mutate(
    day = ceiling(start_time/(24*60)),
    start_time_day = start_time %% (24*60) %>% seconds_to_period(),
    end_time_day = end_time %% (24*60) %>% seconds_to_period(),
    # replication = replication %>% as_factor(),
  ) -> arrivals.df1
arrivals.df1 %>% filter(replication == 1) %>% mutate(start_time_day = start_time_day %>% round(3), end_time_day = end_time_day %>% round(3))



# turn what's above into a function so it can be done for 2a and 2b
# get_arrivals <- function(mon_attributes) {
#   mon_attributes %>%
#     filter(start_time != -1) %>% # n sei pq e q isto acontece
#     arrange(start_time) %>% 
#     select(-activity_time) %>% 
#     mutate(
#       day = ceiling(start_time/(24*60)),
#       start_time_day = start_time %% (24*60) %>% seconds_to_period(),
#       end_time_day = end_time %% (24*60) %>% seconds_to_period(),
#       # replication = replication %>% as_factor(),
#     )
# }
# 
# pprint_arrivals <- function (arrivals.df1) {
#   arrivals.df1 %>% filter(replication == 1) %>% mutate(start_time_day = start_time_day %>% round(3), end_time_day = # end_time_day %>% round(3))
# }
# analise de eficiencia
# numero de chamadas perdidas
# add day to atributes from arrivals.df1
attributes %>% 
  left_join(arrivals.df1 %>% select(name, replication, day, start_time, end_time, end_time_day), by = c("name", "replication")) %>% 
  rename(atribute_time = time, pessoa_start_time = start_time, pessoa_end_time = end_time) %>% 
  select(atribute_time, name, key, value, replication, day, pessoa_start_time, pessoa_end_time) -> attributes.df1
attributes.df1 
# pessoas e os seus tipos de chamada
attributes.df1 %>% 
  filter(key == "tipo_de_contacto") %>% 
  select(name, replication, value) %>% 
  mutate(value = ifelse(value == tipo_de_contacto.TELEFONE, "telefone", "app")) %>%
  rename(contacto = value) -> contactos.df1
# percentagem de chamadas perdidas
attributes.df1 %>% 
  filter(key == "tipo_de_contacto", value == tipo_de_contacto.TELEFONE) %>% 
  left_join(attributes.df1 %>% filter(key == "phone_rejected") %>% mutate(gave_up = key) %>% select(name, replication, gave_up), join_by("name", "replication")) %>% 
  mutate(gave_up = if_else(gave_up %>% is.na, T, F), replication = replication %>% as_factor) %>% 
  select(name, replication, pessoa_end_time, gave_up) -> phone_calls
phone_calls
# phone_calls rate per replication
phone_calls %>% 
  group_by(replication) %>% 
  summarise(phone_calls = n(), gave_up = sum(gave_up)) %>% 
  mutate(phone_calls_per_hour = phone_calls/10, gave_up_rate = gave_up/phone_calls) %>% 
  arrange(desc(gave_up_rate)) -> phone_calls_rate
phone_calls_rate

phone_calls_rate %>% 
  ggplot(aes(x = reorder(replication, gave_up_rate), y = gave_up_rate)) +
  geom_col() + 
  ylim(0, 1) +
  theme(axis.text.x = element_text(angle = 70, hjust = 1)) +
  geom_hline(aes(yintercept = min(gave_up_rate)), linetype = "dashed", color = "red") +
  geom_hline(aes(yintercept = max(gave_up_rate)), linetype = "dashed", color = "blue") +
  xlab("Replication") + 
  ylab("Gave up rate")

phone_calls_rate %>% 
  summarise(
    min = min(phone_calls_per_hour), 
    mean = mean(phone_calls_per_hour), 
    max = max(phone_calls_per_hour)
    )
# evolucao ao longo do dia DO QUE?
attributes.df1 %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none")

# get queue size histogram
resources %>% 
  mutate(day = ceiling(time/(24*60))) %>% 
  group_by(replication) %>%
  reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
  mutate(replication = replication %>% as_factor()) %>% 
  ggplot(aes(x = time, y = queue_size)) +
  geom_step(aes(color = replication), alpha = 0.3) + 
  geom_smooth(method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none")


resources %>% 
  mutate(day = ceiling(time/(24*60))) %>% 
  group_by(replication) %>%
  reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
  mutate(replication = replication %>% as_factor()) %>% 
  ggplot(aes(x = time, y = queue_size)) +
  geom_step(aes(color = replication), alpha = 0.3) + 
  geom_smooth(method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ylim(0,250)

NA
NA
# join these 2 plots
attributes.df1 %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ylim(0,875)

  
attributes.df1 %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none")



attributes.df1 %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +

  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +

  theme(legend.position = "none")

# tempo de espera daqueles que concluiram, speardao por chamada e mensagem
attributes %>% 
  filter(key == "atendido") %>% 
  left_join(arrivals.df1 %>% select(start_time, name, replication), join_by("name", "replication")) %>% 
  left_join(contactos.df1, join_by("name", "replication")) %>% 
  mutate(time_waiting = time - start_time) %>% 
  select(name, value, replication, time_waiting, contacto) -> waiting_times
waiting_times
# histograma (n tou a perceber pq é q há waiting times maiores que 5)
waiting_times %>% 
  filter(contacto == "telefone") %>%
  ggplot(aes(x = time_waiting)) +
  geom_histogram(bins = 100) +
  theme(legend.position = "none")


waiting_times %>% 
  filter(contacto == "app") %>%
  ggplot(aes(x = time_waiting/60)) +
  geom_histogram(bins = 100) +
  theme(legend.position = "none")

# regular waiting time stats without graphs
waiting_times %>% 
  group_by(contacto) %>% 
  summarise(mean = mean(time_waiting), sd = sd(time_waiting), median = median(time_waiting)) 
waiting_times %>% 
  group_by(contacto) %>% 
  summarise(quantile_10 = quantile(time_waiting, 0.1), quantile_20 = quantile(time_waiting, 0.2), quantile_30 = quantile(time_waiting, 0.3), quantile_40 = quantile(time_waiting, 0.4), quantile_50 = quantile(time_waiting, 0.5), quantile_60 = quantile(time_waiting, 0.6), quantile_70 = quantile(time_waiting, 0.7), quantile_80 = quantile(time_waiting, 0.8), quantile_90 = quantile(time_waiting, 0.9), quantile_95 = quantile(time_waiting, 0.95), quantile_99 = quantile(time_waiting, 0.99))
resources %>% plot(metric = "utilization")

resources %>% plot(metric = "usage", steps = T)

Pergunta 2

Alíneas com mudanças relativas às interrupções

# mudanças
envs <- lapply(1:50, function(i) {
  env <<- simmer("hospital") 
  env %>% 
    add_generator("pessoa_de_manha", pessoa, from_to(8*60, 10*60, \() rexp(1, (120/2)/60), every = 24*60, arrive = F), mon = 2) %>% 
    add_generator("pessoa_de_tarde", pessoa, from_to(10*60, 16*60, \() rexp(1, (240/6)/60), every = 24*60, arrive = F), mon = 2) %>% 
    add_generator("pessoa_de_noite", pessoa, from_to(16*60, 18*60, \() rexp(1, (40/2)/60), every = 24*60, arrive = F), mon = 2) %>% 
    add_resource("administrador", schedule(
      c(8 , 9 , 9.5 , 12 , 15.5 ,18)*60, 
      c(  1 , 2,    4,   3,     1,  0), 
      period = 24*60
    )) %>%
    run(24*60*5)
})

envs %>% simmer.plot::get_mon_arrivals(ongoing = T) -> arrivals
envs %>% simmer.plot::get_mon_attributes() -> attributes
envs %>% simmer.plot::get_mon_resources() -> resources

2.b)

# b)
arrivals %>%
  filter(start_time != -1) %>% # n sei pq e q isto acontece
  arrange(start_time) %>% 
  select(-activity_time) %>% 
  mutate(
    day = ceiling(start_time/(24*60)),
    start_time_day = start_time %% (24*60) %>% seconds_to_period(),
    end_time_day = end_time %% (24*60) %>% seconds_to_period(),
    replication = replication %>% as_factor(),
  ) -> arrivals.df2b
arrivals.df2b %>% filter(replication == 1) %>% mutate(start_time_day = start_time_day %>% round(3), end_time_day = end_time_day %>% round(3))
arrivals.df2b$replication <- as.integer(as.character(arrivals.df2b$replication))

# tempo de espera daqueles que concluiram, speardao por chamada e mensagem
arrivals.df2b %>% 
  filter(finished == TRUE) %>% 
  mutate(waiting_time = end_time - start_time) %>% 
  left_join(attributes %>% filter(key == "tipo_de_contacto"), by = c("name", "replication")) %>% 
  mutate(tipo_de_contacto = ifelse(value == tipo_de_contacto.TELEFONE, "telefone", "mensagem") %>% as_factor, replication = replication %>% as_factor) %>%
  select(name, replication, waiting_time, tipo_de_contacto) -> waiting_times.2b
waiting_times.2b
# analise de eficiencia
# numero de chamadas perdidas
# add day to atributes from arrivals.df2b
attributes %>% 
  left_join(arrivals.df2b %>% select(name, replication, day, start_time, end_time, end_time_day), by = c("name", "replication")) %>% 
  rename(atribute_time = time, pessoa_start_time = start_time, pessoa_end_time = end_time) %>% 
  select(atribute_time, name, key, value, replication, day, pessoa_start_time, pessoa_end_time) -> attributes.df2b
attributes.df2b 
# pessoas e os seus tipos de chamada
attributes.df2b %>% 
  filter(key == "tipo_de_contacto") %>% 
  select(name, replication, value) %>% 
  mutate(value = ifelse(value == tipo_de_contacto.TELEFONE, "telefone", "app")) %>%
  rename(contacto = value) -> contactos.df2b
# percentagem de chamadas perdidas
attributes.df2b %>% 
  filter(key == "tipo_de_contacto", value == tipo_de_contacto.TELEFONE) %>% 
  left_join(attributes.df2b %>% filter(key == "phone_rejected") %>% mutate(gave_up = key) %>% select(name, replication, gave_up), join_by("name", "replication")) %>% 
  mutate(gave_up = if_else(gave_up %>% is.na, T, F), replication = replication %>% as_factor) %>% 
  select(name, replication, pessoa_end_time, gave_up) -> phone_calls
phone_calls
# phone_calls rate per replication
phone_calls %>% 
  group_by(replication) %>% 
  summarise(phone_calls = n(), gave_up = sum(gave_up)) %>% 
  mutate(phone_calls_per_hour = phone_calls/10, gave_up_rate = gave_up/phone_calls) %>% 
  arrange(desc(gave_up_rate)) -> phone_calls_rate
phone_calls_rate

phone_calls_rate %>% 
  ggplot(aes(x = reorder(replication, gave_up_rate), y = gave_up_rate)) +
  geom_col() + 
  ylim(0, 1) +
  theme(axis.text.x = element_text(angle = 70, hjust = 1)) +
  geom_hline(aes(yintercept = min(gave_up_rate)), linetype = "dashed", color = "red") +
  geom_hline(aes(yintercept = max(gave_up_rate)), linetype = "dashed", color = "blue") +
  xlab("Replication") + 
  ylab("Gave up rate")

phone_calls_rate %>% 
  summarise(
    min = min(phone_calls_per_hour), 
    mean = mean(phone_calls_per_hour), 
    max = max(phone_calls_per_hour)
    )
# evolucao ao longo do dia DO QUE?
attributes.df2b %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none")

# get queue size histogram
resources %>% 
  mutate(day = ceiling(time/(24*60))) %>% 
  group_by(replication) %>%
  reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
  mutate(replication = replication %>% as_factor()) %>% 
  ggplot(aes(x = time, y = queue_size)) +
  geom_step(aes(color = replication), alpha = 0.3) + 
  geom_smooth(method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none")


resources %>% 
  mutate(day = ceiling(time/(24*60))) %>% 
  group_by(replication) %>%
  reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
  mutate(replication = replication %>% as_factor()) %>% 
  ggplot(aes(x = time, y = queue_size)) +
  geom_step(aes(color = replication), alpha = 0.3) + 
  geom_smooth(method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ylim(0,250)

NA
# join these 2 plots
attributes.df2b %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ylim(0,875)

  
attributes.df2b %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ggtitle("Tamanho da fila e chamadas rejeitadas 2b) por dia") +
  xlab("Tempo de simulação (horas)") + 
  ylab("Número de chamadas rejeitadas")




attributes.df2b %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +

  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  theme(legend.position = "none") +
  ggtitle("Tamanho da fila e chamadas rejeitadas 2b) nos 5 dias") +
  xlab("Tempo de simulação (horas)") + 
  ylab("Número de chamadas rejeitadas")

# tempo de espera daqueles que concluiram, speardao por chamada e mensagem
attributes %>% 
  filter(key == "atendido") %>% 
  left_join(arrivals.df2b %>% select(start_time, name, replication), join_by("name", "replication")) %>% 
  left_join(contactos.df2b, join_by("name", "replication")) %>% 
  mutate(time_waiting = time - start_time) %>% 
  select(name, value, replication, time_waiting, contacto) -> waiting_times
waiting_times
# histograma (n tou a perceber pq é q há waiting times maiores que 5)
waiting_times %>% 
  filter(contacto == "telefone") %>%
  ggplot(aes(x = time_waiting)) +
  geom_histogram(bins = 100) +
  theme(legend.position = "none")+
  ylim(0, 1000)


waiting_times %>% 
  filter(contacto == "app") %>%
  ggplot(aes(x = time_waiting/60)) +
  geom_histogram(bins = 100) +
  theme(legend.position = "none")

# regular waiting time stats without graphs
waiting_times %>% 
  group_by(contacto) %>% 
  summarise(mean = mean(time_waiting), sd = sd(time_waiting), median = median(time_waiting)) 
waiting_times %>% 
  group_by(contacto) %>% 
  summarise(quantile_10 = quantile(time_waiting, 0.1), quantile_20 = quantile(time_waiting, 0.2), quantile_30 = quantile(time_waiting, 0.3), quantile_40 = quantile(time_waiting, 0.4), quantile_50 = quantile(time_waiting, 0.5), quantile_60 = quantile(time_waiting, 0.6), quantile_70 = quantile(time_waiting, 0.7), quantile_80 = quantile(time_waiting, 0.8), quantile_90 = quantile(time_waiting, 0.9), quantile_95 = quantile(time_waiting, 0.95), quantile_99 = quantile(time_waiting, 0.99))
resources %>% plot(metric = "utilization")

resources %>% plot(metric = "usage", steps = T)

2.a) - - - - - - - - - - - -

# a) set_prioritization(\() if (get_attribute(env, "tipo_de_consulta") == tipo_de_consulta.TELEFONICA) c(1, -1, FALSE) else c(0 , -1, FALSE))
pessoa__ <- join(
  pessoa[1:2], 
  trajectory("change_prio") %>% 
     set_prioritization(\() if (get_attribute(env, "tipo_de_contacto") == tipo_de_contacto.TELEFONE) c(1, 1, FALSE) else c(0 , 0, FALSE)),
  pessoa[-(1:3)]
)
pessoa__
trajectory: pessoa, 10 activities
{ Activity: SetAttribute | keys: [tipo_de_consulta], values: function(), global: 0, mod: N, init: 0 }
{ Activity: SetAttribute | [contacto] keys: [tipo_de_contacto], values: function(), global: 0, mod: N, init: 0 }
{ Activity: SetPrior     | values: function(), mod: N }
{ Activity: RenegeIn     | t: function(), keep_seized: 0 }
  Fork 1, stop,  trajectory: anonymous, 1 activities
  { Activity: SetAttribute | keys: [phone_rejected], values: [1], global: 0, mod: N, init: 0 }
{ Activity: Seize        | resource: administrador, amount: 1 }
{ Activity: RenegeAbort  |  }
{ Activity: SetAttribute | keys: [atendido], values: [1], global: 0, mod: N, init: 0 }
{ Activity: Timeout      | delay: function() }
{ Activity: Release      | resource: administrador, amount: 1 }
envs <- lapply(1:50, function(i) {
  env <<- simmer("hospital") 
  env %>% 
    add_generator("pessoa_de_manha", pessoa__, from_to(8*60, 10*60, \() rexp(1, (120/2)/60), every = 24*60, arrive = F), mon = 2) %>% 
    add_generator("pessoa_de_tarde", pessoa__, from_to(10*60, 16*60, \() rexp(1, (240/6)/60), every = 24*60, arrive = F), mon = 2) %>% 
    add_generator("pessoa_de_noite", pessoa__, from_to(16*60, 18*60, \() rexp(1, (40/2)/60), every = 24*60, arrive = F), mon = 2) %>% 
    add_resource("administrador", schedule(
      c(8 , 9 , 9.5 , 12 , 15.5 ,18)*60, 
      c(  1 , 2,    4,   3,     1,  0), 
      period = 24*60
    )) %>%
    run(24*60*5)
})

envs %>% simmer.plot::get_mon_arrivals(ongoing = T) -> arrivals
envs %>% simmer.plot::get_mon_attributes() -> attributes
envs %>% simmer.plot::get_mon_resources() -> resources
arrivals %>%
  filter(start_time != -1) %>% # n sei pq e q isto acontece
  arrange(start_time) %>% 
  select(-activity_time) %>% 
  mutate(
    day = ceiling(start_time/(24*60)),
    start_time_day = start_time %% (24*60) %>% seconds_to_period(),
    end_time_day = end_time %% (24*60) %>% seconds_to_period(),
    replication = replication %>% as_factor(),
  ) -> arrivals.df2a
arrivals.df2a %>% filter(replication == 1) %>% mutate(start_time_day = start_time_day %>% round(3), end_time_day = end_time_day %>% round(3)) 
arrivals.df2a$replication <- as.integer(as.character(arrivals.df2a$replication))

# analise de eficiencia
attributes %>% 
  left_join(arrivals.df2a, by = c("name", "replication") ) %>% 
  select(name, key, value, replication, day, end_time, end_time_day) -> atributes.df2a
atributes.df2a %>% 
  filter(key == "phone_rejected")
# analise de eficiencia
# numero de chamadas perdidas
# add day to atributes from arrivals.df2a
attributes %>% 
  left_join(arrivals.df2a %>% select(name, replication, day, start_time, end_time, end_time_day), by = c("name", "replication")) %>% 
  rename(atribute_time = time, pessoa_start_time = start_time, pessoa_end_time = end_time) %>% 
  select(atribute_time, name, key, value, replication, day, pessoa_start_time, pessoa_end_time) -> attributes.df2a
attributes.df2a 
# pessoas e os seus tipos de chamada
attributes.df2a %>% 
  filter(key == "tipo_de_contacto") %>% 
  select(name, replication, value) %>% 
  mutate(value = ifelse(value == tipo_de_contacto.TELEFONE, "telefone", "app")) %>%
  rename(contacto = value) -> contactos.df2a
# percentagem de chamadas perdidas
attributes.df2a %>% 
  filter(key == "tipo_de_contacto", value == tipo_de_contacto.TELEFONE) %>% 
  left_join(attributes.df2a %>% filter(key == "phone_rejected") %>% mutate(gave_up = key) %>% select(name, replication, gave_up), join_by("name", "replication")) %>% 
  mutate(gave_up = if_else(gave_up %>% is.na, T, F), replication = replication %>% as_factor) %>% 
  select(name, replication, pessoa_end_time, gave_up) -> phone_calls
phone_calls
# phone_calls rate per replication
phone_calls %>% 
  group_by(replication) %>% 
  summarise(phone_calls = n(), gave_up = sum(gave_up)) %>% 
  mutate(phone_calls_per_hour = phone_calls/10, gave_up_rate = gave_up/phone_calls) %>% 
  arrange(desc(gave_up_rate)) -> phone_calls_rate
phone_calls_rate

phone_calls_rate %>% 
  ggplot(aes(x = reorder(replication, gave_up_rate), y = gave_up_rate)) +
  geom_col() + 
  ylim(0, 1) +
  theme(axis.text.x = element_text(angle = 70, hjust = 1)) +
  geom_hline(aes(yintercept = min(gave_up_rate)), linetype = "dashed", color = "red") +
  geom_hline(aes(yintercept = max(gave_up_rate)), linetype = "dashed", color = "blue") +
  xlab("Replication") + 
  ylab("Gave up rate")

phone_calls_rate %>% 
  summarise(
    min = min(phone_calls_per_hour), 
    mean = mean(phone_calls_per_hour), 
    max = max(phone_calls_per_hour)
    )
# evolucao ao longo do dia DO QUE?
attributes.df2a %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none")

# get queue size histogram
resources %>% 
  mutate(day = ceiling(time/(24*60))) %>% 
  group_by(replication) %>%
  reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
  mutate(replication = replication %>% as_factor()) %>% 
  ggplot(aes(x = time, y = queue_size)) +
  geom_step(aes(color = replication), alpha = 0.3) + 
  geom_smooth(method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none")


resources %>% 
  mutate(day = ceiling(time/(24*60))) %>% 
  group_by(replication) %>%
  reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
  mutate(replication = replication %>% as_factor()) %>% 
  ggplot(aes(x = time, y = queue_size)) +
  geom_step(aes(color = replication), alpha = 0.3) + 
  geom_smooth(method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ylim(0,250)

NA
# join these 2 plots
attributes.df2a %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ylim(0,875)

  
attributes.df2a %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  facet_wrap(vars(day), scale = "free") +
  theme(legend.position = "none") +
  ggtitle("Tamanho da fila e chamadas rejeitadas 2a) por dia") +
  xlab("Tempo de simulação (horas)") + 
  ylab("Número de chamadas rejeitadas")




attributes.df2a %>% 
  filter(key == "phone_rejected") %>% 
  mutate(day = day %>% as_factor(), replication = replication %>% as_factor) %>% 
  group_by(replication) %>% 
  reframe(time_rejected = pessoa_end_time/60, sumcum_per_day = cumsum(value), day = day) %>% 
  ggplot(aes(x = time_rejected, y = sumcum_per_day)) +
  geom_line(aes(color = replication), alpha = 0.3) +
  geom_smooth() +

  theme(legend.position = "none") +
  geom_step(data = resources %>% 
              mutate(day = ceiling(time/(24*60))) %>% 
              group_by(replication) %>%
              reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
              mutate(replication = replication %>% as_factor()), 
            aes(x = time, y = queue_size), alpha = 0.3) + 
  geom_smooth(data = resources %>% 
                mutate(day = ceiling(time/(24*60))) %>% 
                group_by(replication) %>%
                reframe(day = day %>% as_factor(), time = time/60, queue_size = queue) %>% 
                mutate(replication = replication %>% as_factor()), 
              aes(x = time, y = queue_size), method="lm") +
  theme(legend.position = "none") +
  ggtitle("Tamanho da fila e chamadas rejeitadas 2a) nos 5 dias") +
  xlab("Tempo de simulação (horas)") + 
  ylab("Número de chamadas rejeitadas")

# tempo de espera daqueles que concluiram, speardao por chamada e mensagem
attributes %>% 
  filter(key == "atendido") %>% 
  left_join(arrivals.df2a %>% select(start_time, name, replication), join_by("name", "replication")) %>% 
  left_join(contactos.df2a, join_by("name", "replication")) %>% 
  mutate(time_waiting = time - start_time) %>% 
  select(name, value, replication, time_waiting, contacto) -> waiting_times
waiting_times
# histograma (n tou a perceber pq é q há waiting times maiores que 5)
waiting_times %>% 
  filter(contacto == "telefone") %>%
  ggplot(aes(x = time_waiting)) +
  geom_histogram(bins = 100) +
  theme(legend.position = "none")+ 
  ylim(0, 1000)


waiting_times %>% 
  filter(contacto == "app") %>%
  ggplot(aes(x = time_waiting/60)) +
  geom_histogram(bins = 100) +
  theme(legend.position = "none")

# regular waiting time stats without graphs
waiting_times %>% 
  group_by(contacto) %>% 
  summarise(mean = mean(time_waiting), sd = sd(time_waiting), median = median(time_waiting)) 
waiting_times %>% 
  group_by(contacto) %>% 
  summarise(quantile_10 = quantile(time_waiting, 0.1), quantile_20 = quantile(time_waiting, 0.2), quantile_30 = quantile(time_waiting, 0.3), quantile_40 = quantile(time_waiting, 0.4), quantile_50 = quantile(time_waiting, 0.5), quantile_60 = quantile(time_waiting, 0.6), quantile_70 = quantile(time_waiting, 0.7), quantile_80 = quantile(time_waiting, 0.8), quantile_90 = quantile(time_waiting, 0.9), quantile_95 = quantile(time_waiting, 0.95), quantile_99 = quantile(time_waiting, 0.99))
resources %>% plot(metric = "utilization")

resources %>% plot(metric = "usage", steps = T)

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCmlmIChSLnZlcnNpb24kbWFqb3IgPCA0KSB7DQogIHdhcm5pbmcoIk5vdCB1c2luZyBSIDQuMC4gSXQgc2hvdWxkLiIpDQp9DQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQ0KcGFjbWFuOjpwX2xvYWQoDQogIHRpZHl2ZXJzZSwgIyANCiAgY29uZmxpY3RlZCwNCiAgbHVicmlkYXRlLA0KICBzaW1tZXIsDQogIHNpbW1lci5wbG90LA0KICBzdmdsaXRlDQopDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY29uZmxpY3RlZCkNCmxpYnJhcnkobHVicmlkYXRlKQ0KY29uZmxpY3RlZDo6Y29uZmxpY3RfcHJlZmVyKCJmaWx0ZXIiLCAiZHBseXIiLCBjKCJiYXNlIiwgInN0YXRzIikpDQpsaWJyYXJ5KHNpbW1lcikNCmxpYnJhcnkoc2ltbWVyLnBsb3QpDQpjb25mbGljdHNfcHJlZmVyKHNpbW1lcjo6cm9sbGJhY2spDQpjb25mbGljdHNfcHJlZmVyKHNpbW1lcjo6bm93KQ0KY29uZmxpY3RzX3ByZWZlcihkcGx5cjo6c2VsZWN0KQ0Kb3B0aW9ucygNCiAgcmVwb3M9YyhDUkFOPSJodHRwczovL2NyYW4ucmFkaWNhbGRldmVsb3AuY29tLyIpLA0KICB0aWR5dmVyc2UucXVpZXQgPSBUUlVFDQopDQojIHRoaXMgd2lsbCBiZSBzdWJzdGl0dXRlZCBiZWxvdyBhbmQgbGl0ZXJhbGx5IGp1c3QgaXMgaGVyZSBiZWNhdXNlIHNpbW1lciBkb2Vzbid0IGtub3cgaG93IHRvIGltcGxlbWVudCBwaXBlcyBhbmQgSSBoYXRlIGl0IGhlcmUNCg0KZW52IDwtIHNpbW1lcigiaG9zcGl0YWwiKSANCmBgYA0KDQoNCiMjIFByb2Nlc3NvDQoNClVtIHBhY2llbnRlIHF1ZSBlc3TDoSBlbSBsaXN0YSBkZSBlc3BlcmEgcGFyYSBzZXIgb3BlcmFkbzoNCg0KMS4gRW50cmEgZW0gY29udGFjdG8gY29tIG8gc2VydmnDp28gZGUgY2lydXJnaWEsIHbDoXJpb3MgZGlhcyBhbnRlcyBkYSBzdWEgb3BlcmHDp8OjbywgDQogICogQXRyYXbDqXMgZGUgdW1hIGNoYW1hZGEgdGVsZWbDs25pY2Egb3UgDQogICogQXRyYXbDqXMgZG8gZW52aW8gZGUgdW1hIG1lbnNhZ2VtIGVzY3JpdGEgbnVtYSBhcHAuDQoNCjIuIE9zIHNlcnZpw6dvcyBhZG1pbmlzdHJhdGl2b3MgYXRlbmRlbSBhcyBjaGFtYWRhcyBvdSBsw6plbSBhIG1lbnNhZ2VtIHZpYSBhcHAgZSwgbWVkaWFudGUNCiAgKiBhIGluZm9ybWHDp8OjbyBwcmVzZW50ZSBubyBzaXN0ZW1hIGRhZGEgcGVsbyBtw6lkaWNvIGFzc2lzdGVudGUgZSANCiAgKiBhIGluZm9ybWHDp8OjbyBmb3JuZWNpZGEgcGVsbyBwYWNpZW50ZSwgDQpvIGFkbWluaXN0cmF0aXZvIGRlY2lkZSBzZSBhZ2VuZGENCiAgKiB1bWEgY29uc3VsdGEgcHJlc2VuY2lhbCBhbnRlcyBkYSBjaXJ1cmdpYSwgb3UNCiAgKiB1bWEgY29uc3VsdGEgdGVsZWbDs25pY2EsIG91DQogICogbsOjbyBow6EgbmVjZXNzaWRhZGUgZGUgcXVhbHF1ZXIgY29uc3VsdGEuIA0KDQpDb250dWRvLCBlc3RlIHByb2NlZGltZW50byBuZWNlc3NpdGEgZGUgbWVsaG9yaWFzLiANCk8gb2JqZWN0aXZvIGRlc3RlIGVzdHVkbyDDqSBvcHRpbWl6YXIgYSBnZXN0w6NvIGRvcyBjb250YWN0b3MgKHRlbXBvIHRvdGFsIGRvIHByb2Nlc3NvIGUgdG90YWwgZGUgY2hhbWFkYXMgcGVyZGlkYXMpDQoNCiMjIFNpdHVhw6fDo28gYXR1YWwNCg0KMS4gQXBlbmFzIDIgYWRtaW5pc3RyYXRpdm9zIG5vIHNlcnZpw6dvLCBhbWJvcyBjb21lw6dhbSDDoHMgOGggZSBhY2FiYW0gw6BzIDE4aCAoMTBoIGRlIHRyYWJhbGhvKSBkZSBjYWRhIGRpYSDDunRpbC4NCg0KMi4gTyB0ZW1wbyBlbnRyZSBjaGVnYWRhcyB0ZW0gZXhwKDEvNDApIChwcSBtZWRpYSBkZSA0MC9oKQ0KDQozLiBUaXBvIGRlIGNvbnRhY3RvOg0KKiBwcm9iIGRlIHRlbGVmb25lID0gMC44NQ0KKiBwcm9iIGRlIGFwcCA9IDAuMTUNCg0KNC4gVGlwbyBkZSBjb25zdWx0YTogDQoqIDAuMyBuw6NvIMOpIGFnZW5kYWRvIGNvbnN1bHRhLCANCiogMC42IGNvbnN1bGEgcG9yIHRlbGVmb25lLCANCiogMC4xIHByZXNlbmNpYWwNCg0KNS4gVGVtcG8gZGUgDQoqIENvbnZlcnNhIGFudGVzIGRhIGRlY2lzYW8gw6kgbm9ybSg0LDFeMikNCiogVG9tYWRhIGRlIGRlY2lzYW8gZG8gYWRtaW5zdHJhdGl2byDDqSBub3JtKDEsMC4yNV4yKQ0KKiBUZW1wbyBkZSBhZ2VuZGFtZW50byBTRSBORUNFU1PDgVJJTyDDqSBub3JtKDEsMC4yNV4yKQ0KDQo2LiA1byB0ZWxlZm9uZSBkZXNsaWdhIGFwb3MgNSBtaW51dG9zIGRlIGVzcGVyYSAoZSBmaWNhIGNoYW1hZGEgcGVyZGlkYSkgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHNpbW1lcikNCmxpYnJhcnkoc2ltbWVyLmJyaWNrcykNCmxpYnJhcnkoc2ltbWVyLnBsb3QpDQpjb25mbGljdHNfcHJlZmVyKHNpbW1lcjo6cm9sbGJhY2spDQpjb25mbGljdHNfcHJlZmVyKHNpbW1lcjo6bm93KQ0KY29uZmxpY3RzX3ByZWZlcihkcGx5cjo6c2VsZWN0KQ0KIyB0aGlzIHdpbGwgYmUgc3Vic3RpdHV0ZWQgYmVsb3cgYW5kIGxpdGVyYWxseSBqdXN0IGlzIGhlcmUgYmVjYXVzZSBzaW1tZXIgZG9lc24ndCBrbm93IGhvdyB0byBpbXBsZW1lbnQgcGlwZXMgYW5kIEkgaGF0ZSBpdCBoZXJlDQplbnYgPC0gc2ltbWVyKCJob3NwaXRhbCIpIA0KYGBgDQpSZXNvdXJjZXM6DQogIFNlcnZlcjogTyBob3NwaXRhbCBjb20gMiBhZG1pbmlzdHJhZG9yZXMNCiAgUXVldWU6IEZpbGEgZGUgZXNwZXJhIGNvbSBwcmlvcmlkYWRlIGRlIGNsaWVudHMgZW0gY2hhbWFkYQ0KICAgICBOw6NvIGjDoSB2YW50Z2VtIGVtIGVzY29saGVyIGEgYXBwIHF1YW5kbyBow6EgY2hhbWFkYSBkZSBlc3BlcmENCk1hbmFnZXI6IHZhaSBhdHJpYnVpciBvIHRpcG8gZGUgY29uc3VsdGEgKGUgVmFpIGFsdGVyYXIgbyB0aXBvIGRlIGNsaWVudGUgc2UgZWxlIGZvciByZWplaXRhZG8gbmEgY2hhbWFkYT8pDQpHZXJhZG9yOiB2YWkgZ2VyaXIgbm92b3MgYXJyaXZhbHMgKGV4cCgxLzQwKSkNCmFycml2YWw6IGNhZGEgcGFjaWVudGUgKHF1ZSBsZXZhIHVtYSB0cmFqZWN0b3J5KQ0KdHJhamVjdG9yeTogYSByZWNlaXRhIGRlIGNhZGEgYXJyaXZhbA0KDQpgYGB7cn0NCiMgYXRyaWJ1dGVzIG5lZWQgdG8gYmUgbnVtZXJpYw0KdGlwb19kZV9jb25zdWx0YS5QUkVTRU5DSUFMIDwtIDENCnRpcG9fZGVfY29uc3VsdGEuVEVMRUZPTklDQSA8LSAyDQp0aXBvX2RlX2NvbnN1bHRhLk5BT19BR0VOREFEQSA8LSAzDQp0aXBvc19kZV9jb25zdWx0YSA8LSBjKHRpcG9fZGVfY29uc3VsdGEuUFJFU0VOQ0lBTCwgdGlwb19kZV9jb25zdWx0YS5URUxFRk9OSUNBLCB0aXBvX2RlX2NvbnN1bHRhLk5BT19BR0VOREFEQSkNCnRpcG9zX2RlX2NvbnN1bHRhLnByb2JzIDwtIGMoMC4xLCAwLjYsIDAuMykNCg0KdGlwb19kZV9jb250YWN0by5URUxFRk9ORSA8LSAxDQp0aXBvX2RlX2NvbnRhY3RvLkFQUCA8LSAyDQp0aXBvc19kZV9jb250YWN0byA8LSBjKHRpcG9fZGVfY29udGFjdG8uVEVMRUZPTkUsIHRpcG9fZGVfY29udGFjdG8uQVBQKQ0KdGlwb3NfZGVfY29udGFjdG8ucHJvYnMgPC0gYygwLjg1LCAwLjE1KQ0KDQp0ZW1wb19hdGVuZGlkbyA8LSBmdW5jdGlvbigpIHsNCiAgbWVhbl90aW1lX3RyaWFnZW0gPC0gcm5vcm0oMSwgbWVhbiA9IDQsIHNkID0gMSkNCiAgbWVhbl90aW1lX2RlY2lzaW9uIDwtIHJub3JtKDEsIG1lYW4gPSAxLCBzZCA9IDAuMjUpDQogIHRpbWVfdG9fdGltZW91dCA8LSBtZWFuX3RpbWVfdHJpYWdlbSArIG1lYW5fdGltZV9kZWNpc2lvbg0KICANCiAgaWYgKGdldF9hdHRyaWJ1dGUoZW52LCAidGlwb19kZV9jb25zdWx0YSIpICE9IHRpcG9fZGVfY29uc3VsdGEuTkFPX0FHRU5EQURBKSB7DQogICAgdGltZV90b190aW1lb3V0IDwtIHRpbWVfdG9fdGltZW91dCArIHJub3JtKDEsIG1lYW4gPSAxLCBzZCA9IDAuMjUpDQogIH0NCiAgDQogIHJldHVybih0aW1lX3RvX3RpbWVvdXQpDQp9DQoNCmludGltZSA8LSBmdW5jdGlvbigpIHsNCiAgbm93KGVudiklJSAyNCo2MCAtPiBuDQogIHJldHVybig4KjYwIDwgbiAmIG4gPCAxOCo2MCkNCn0NCmBgYA0KDQoNCmBgYHtyfQ0KcGVzc29hIDwtIHRyYWplY3RvcnkoInBlc3NvYSIpICU+JQ0KICAjIHNldF9hdHRyaWJ1dGUoInRpbWVzX3Bob25lX3JlamVjdGVkIiwgMCkgJT4lIA0KICAjIHNldF9hdHRyaWJ1dGUoInBob25lX3JlamVjdGVkIiwgMCkgJT4lDQogIHNldF9hdHRyaWJ1dGUoInRpcG9fZGVfY29uc3VsdGEiLCBcKCkgc2FtcGxlKHRpcG9zX2RlX2NvbnN1bHRhLCAxLCBwcm9iID0gdGlwb3NfZGVfY29uc3VsdGEucHJvYnMpKSAlPiUNCiAgc2V0X2F0dHJpYnV0ZSgidGlwb19kZV9jb250YWN0byIsIFwoKSBzYW1wbGUodGlwb3NfZGVfY29udGFjdG8sIDEsIHByb2IgPSB0aXBvc19kZV9jb250YWN0by5wcm9icyksIHRhZyA9ICJjb250YWN0byIpICU+JQ0KICBzZXRfcHJpb3JpdGl6YXRpb24oXCgpIGlmIChnZXRfYXR0cmlidXRlKGVudiwgInRpcG9fZGVfY29udGFjdG8iKSA9PSB0aXBvX2RlX2NvbnRhY3RvLlRFTEVGT05FKSBjKDEsIC0xLCBGQUxTRSkgZWxzZSBjKDAgLCAtMSwgRkFMU0UpKSAlPiUgIyBQUklPUklUWSwgRFJPUCBQUklPUklUWSwgUkVTVEFSVA0KICAjIGxvZ18oXCgpIHBhc3RlKCJWb3UgcHJhIGZpbGEgYWdvcmEsIGVzdG91IGEgdXNhciIsIGlmZWxzZShnZXRfYXR0cmlidXRlKGVudiwgInRpcG9fZGVfY29udGFjdG8iKSA9PSB0aXBvX2RlX2NvbnRhY3RvLlRFTEVGT05FLCAibyB0ZWxlZm9uZSIsICJhIGFwcCIpKSkgJT4lDQogIHJlbmVnZV9pbihcKCkgaWZlbHNlKGdldF9hdHRyaWJ1dGUoZW52LCAidGlwb19kZV9jb250YWN0byIpID09IHRpcG9fZGVfY29udGFjdG8uVEVMRUZPTkUsIDUsIEluZiksIG91dCA9IA0KICAgICAgICAgICAgICB0cmFqZWN0b3J5KCkgJT4lIHNldF9hdHRyaWJ1dGUoInBob25lX3JlamVjdGVkIiwgMSkNCiAgICAgICAgICAgICAgIyBPIGJvdGFzIG7Do28gZ29zdG91DQogICAgICAgICAgICAgICMgdHJhamVjdG9yeSgicGVzc29hX3RyeV9hZ2FpbiIpICU+JSANCiAgICAgICAgICAgICAgIyBzZXRfYXR0cmlidXRlKCJ0aW1lc19waG9uZV9yZWplY3RlZCIsIFwoKSBnZXRfYXR0cmlidXRlKGVudiwgInRpbWVzX3Bob25lX3JlamVjdGVkIikgKyAxKSAlPiUgDQogICAgICAgICAgICAgICMgIyBsZWF2ZSBpZiBvdXQgb2Ygc2NoZWR1bGUNCiAgICAgICAgICAgICAgIyBsZWF2ZShcKCkge2lmZWxzZShpbnRpbWUoKSwgMCwgMSl9KSAlPiUgDQogICAgICAgICAgICAgICMgIyBsb2dfKFwoKSAiTmFvIGZ1aSBhdGVuZGlkbywgdm91IHZvbHRhciBwcmEgZmlsYSIpICU+JSANCiAgICAgICAgICAgICAgIyByb2xsYmFjayh0YXJnZXQ9ImNvbnRhY3RvIiwgdGltZXMgPSAxKSAjIG7Do28gZnVuY2lvbmEsIG11ZGFyIHByYSBudW1lcm8/DQogICAgICAgICAgICApICU+JQ0KICBzZWl6ZSgiYWRtaW5pc3RyYWRvciIsIDEpICU+JQ0KICByZW5lZ2VfYWJvcnQoKSAlPiUNCiAgIyBsb2dfKFwoKSBwYXN0ZSgiQmVmb3JlIHRpbWVvdXQiKSkgJT4lIA0KICBzZXRfYXR0cmlidXRlKCJhdGVuZGlkbyIsIDEpICU+JSANCiAgdGltZW91dCh0ZW1wb19hdGVuZGlkbykgJT4lIA0KICAjIGxvZ18oXCgpICJBZnRlciB0aW1lb3V0IikgJT4lIA0KICByZWxlYXNlKCJhZG1pbmlzdHJhZG9yIiwgMSkNCiAgIyBsb2dfKFwoKSAiTGVhdmluZyIpDQoNCnBlc3NvYQ0KYGBgDQoNCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KIyA1MCByZXBsaWNhcw0KZW52cyA8LSBsYXBwbHkoMTo1MCwgZnVuY3Rpb24oaSkgew0KICBlbnYgPDwtIHNpbW1lcigiaG9zcGl0YWwiKSANCiAgZW52ICU+JSANCiAgICBhZGRfZ2VuZXJhdG9yKCJwZXNzb2EiLCBwZXNzb2EsIGZyb21fdG8oOCo2MCwgMTgqNjAsIFwoKSByZXhwKDEsIDQwLzYwKSwgZXZlcnkgPSAyNCo2MCwgYXJyaXZlID0gRiksIG1vbiA9IDIpICU+JSANCiAgICBhZGRfcmVzb3VyY2UoImFkbWluaXN0cmFkb3IiLCBzY2hlZHVsZShjKDgqNjAsIDE4KjYwKSwgYygyLCAwKSwgcGVyaW9kID0gMjQqNjApKSAlPiUNCiAgICBydW4oMjQqNjAqNSkNCn0pDQoNCmVudnMgJT4lIHNpbW1lci5wbG90OjpnZXRfbW9uX2Fycml2YWxzKG9uZ29pbmcgPSBUKSAtPiBhcnJpdmFscyANCmVudnMgJT4lIHNpbW1lci5wbG90OjpnZXRfbW9uX2F0dHJpYnV0ZXMoKSAtPiBhdHRyaWJ1dGVzDQplbnZzICU+JSBzaW1tZXIucGxvdDo6Z2V0X21vbl9yZXNvdXJjZXMoKSAtPiByZXNvdXJjZXMNCmBgYA0KDQoNCmBgYHtyfQ0KDQphcnJpdmFscyAlPiUNCiAgZmlsdGVyKHN0YXJ0X3RpbWUgIT0gLTEpICU+JSAjIG4gc2VpIHBxIGUgcSBpc3RvIGFjb250ZWNlDQogIGFycmFuZ2Uoc3RhcnRfdGltZSkgJT4lIA0KICBzZWxlY3QoLWFjdGl2aXR5X3RpbWUpICU+JSANCiAgbXV0YXRlKA0KICAgIGRheSA9IGNlaWxpbmcoc3RhcnRfdGltZS8oMjQqNjApKSwNCiAgICBzdGFydF90aW1lX2RheSA9IHN0YXJ0X3RpbWUgJSUgKDI0KjYwKSAlPiUgc2Vjb25kc190b19wZXJpb2QoKSwNCiAgICBlbmRfdGltZV9kYXkgPSBlbmRfdGltZSAlJSAoMjQqNjApICU+JSBzZWNvbmRzX3RvX3BlcmlvZCgpLA0KICAgICMgcmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCksDQogICkgLT4gYXJyaXZhbHMuZGYxDQphcnJpdmFscy5kZjEgJT4lIGZpbHRlcihyZXBsaWNhdGlvbiA9PSAxKSAlPiUgbXV0YXRlKHN0YXJ0X3RpbWVfZGF5ID0gc3RhcnRfdGltZV9kYXkgJT4lIHJvdW5kKDMpLCBlbmRfdGltZV9kYXkgPSBlbmRfdGltZV9kYXkgJT4lIHJvdW5kKDMpKQ0KDQoNCg0KIyB0dXJuIHdoYXQncyBhYm92ZSBpbnRvIGEgZnVuY3Rpb24gc28gaXQgY2FuIGJlIGRvbmUgZm9yIDJhIGFuZCAyYg0KIyBnZXRfYXJyaXZhbHMgPC0gZnVuY3Rpb24obW9uX2F0dHJpYnV0ZXMpIHsNCiMgICBtb25fYXR0cmlidXRlcyAlPiUNCiMgICAgIGZpbHRlcihzdGFydF90aW1lICE9IC0xKSAlPiUgIyBuIHNlaSBwcSBlIHEgaXN0byBhY29udGVjZQ0KIyAgICAgYXJyYW5nZShzdGFydF90aW1lKSAlPiUgDQojICAgICBzZWxlY3QoLWFjdGl2aXR5X3RpbWUpICU+JSANCiMgICAgIG11dGF0ZSgNCiMgICAgICAgZGF5ID0gY2VpbGluZyhzdGFydF90aW1lLygyNCo2MCkpLA0KIyAgICAgICBzdGFydF90aW1lX2RheSA9IHN0YXJ0X3RpbWUgJSUgKDI0KjYwKSAlPiUgc2Vjb25kc190b19wZXJpb2QoKSwNCiMgICAgICAgZW5kX3RpbWVfZGF5ID0gZW5kX3RpbWUgJSUgKDI0KjYwKSAlPiUgc2Vjb25kc190b19wZXJpb2QoKSwNCiMgICAgICAgIyByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSwNCiMgICAgICkNCiMgfQ0KIyANCiMgcHByaW50X2Fycml2YWxzIDwtIGZ1bmN0aW9uIChhcnJpdmFscy5kZjEpIHsNCiMgICBhcnJpdmFscy5kZjEgJT4lIGZpbHRlcihyZXBsaWNhdGlvbiA9PSAxKSAlPiUgbXV0YXRlKHN0YXJ0X3RpbWVfZGF5ID0gc3RhcnRfdGltZV9kYXkgJT4lIHJvdW5kKDMpLCBlbmRfdGltZV9kYXkgPSAjIGVuZF90aW1lX2RheSAlPiUgcm91bmQoMykpDQojIH0NCg0KYGBgDQoNCmBgYHtyfQ0KIyBhbmFsaXNlIGRlIGVmaWNpZW5jaWENCiMgbnVtZXJvIGRlIGNoYW1hZGFzIHBlcmRpZGFzDQojIGFkZCBkYXkgdG8gYXRyaWJ1dGVzIGZyb20gYXJyaXZhbHMuZGYxDQphdHRyaWJ1dGVzICU+JSANCiAgbGVmdF9qb2luKGFycml2YWxzLmRmMSAlPiUgc2VsZWN0KG5hbWUsIHJlcGxpY2F0aW9uLCBkYXksIHN0YXJ0X3RpbWUsIGVuZF90aW1lLCBlbmRfdGltZV9kYXkpLCBieSA9IGMoIm5hbWUiLCAicmVwbGljYXRpb24iKSkgJT4lIA0KICByZW5hbWUoYXRyaWJ1dGVfdGltZSA9IHRpbWUsIHBlc3NvYV9zdGFydF90aW1lID0gc3RhcnRfdGltZSwgcGVzc29hX2VuZF90aW1lID0gZW5kX3RpbWUpICU+JSANCiAgc2VsZWN0KGF0cmlidXRlX3RpbWUsIG5hbWUsIGtleSwgdmFsdWUsIHJlcGxpY2F0aW9uLCBkYXksIHBlc3NvYV9zdGFydF90aW1lLCBwZXNzb2FfZW5kX3RpbWUpIC0+IGF0dHJpYnV0ZXMuZGYxDQphdHRyaWJ1dGVzLmRmMSANCmBgYA0KDQpgYGB7cn0NCiMgcGVzc29hcyBlIG9zIHNldXMgdGlwb3MgZGUgY2hhbWFkYQ0KYXR0cmlidXRlcy5kZjEgJT4lIA0KICBmaWx0ZXIoa2V5ID09ICJ0aXBvX2RlX2NvbnRhY3RvIikgJT4lIA0KICBzZWxlY3QobmFtZSwgcmVwbGljYXRpb24sIHZhbHVlKSAlPiUgDQogIG11dGF0ZSh2YWx1ZSA9IGlmZWxzZSh2YWx1ZSA9PSB0aXBvX2RlX2NvbnRhY3RvLlRFTEVGT05FLCAidGVsZWZvbmUiLCAiYXBwIikpICU+JQ0KICByZW5hbWUoY29udGFjdG8gPSB2YWx1ZSkgLT4gY29udGFjdG9zLmRmMQ0KYGBgDQoNCg0KYGBge3J9DQojIHBlcmNlbnRhZ2VtIGRlIGNoYW1hZGFzIHBlcmRpZGFzDQphdHRyaWJ1dGVzLmRmMSAlPiUgDQogIGZpbHRlcihrZXkgPT0gInRpcG9fZGVfY29udGFjdG8iLCB2YWx1ZSA9PSB0aXBvX2RlX2NvbnRhY3RvLlRFTEVGT05FKSAlPiUgDQogIGxlZnRfam9pbihhdHRyaWJ1dGVzLmRmMSAlPiUgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKSAlPiUgbXV0YXRlKGdhdmVfdXAgPSBrZXkpICU+JSBzZWxlY3QobmFtZSwgcmVwbGljYXRpb24sIGdhdmVfdXApLCBqb2luX2J5KCJuYW1lIiwgInJlcGxpY2F0aW9uIikpICU+JSANCiAgbXV0YXRlKGdhdmVfdXAgPSBpZl9lbHNlKGdhdmVfdXAgJT4lIGlzLm5hLCBULCBGKSwgcmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKSAlPiUgDQogIHNlbGVjdChuYW1lLCByZXBsaWNhdGlvbiwgcGVzc29hX2VuZF90aW1lLCBnYXZlX3VwKSAtPiBwaG9uZV9jYWxscw0KcGhvbmVfY2FsbHMNCmBgYA0KDQpgYGB7cn0NCiMgcGhvbmVfY2FsbHMgcmF0ZSBwZXIgcmVwbGljYXRpb24NCnBob25lX2NhbGxzICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JSANCiAgc3VtbWFyaXNlKHBob25lX2NhbGxzID0gbigpLCBnYXZlX3VwID0gc3VtKGdhdmVfdXApKSAlPiUgDQogIG11dGF0ZShwaG9uZV9jYWxsc19wZXJfaG91ciA9IHBob25lX2NhbGxzLzEwLCBnYXZlX3VwX3JhdGUgPSBnYXZlX3VwL3Bob25lX2NhbGxzKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhnYXZlX3VwX3JhdGUpKSAtPiBwaG9uZV9jYWxsc19yYXRlDQpwaG9uZV9jYWxsc19yYXRlDQoNCnBob25lX2NhbGxzX3JhdGUgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKHJlcGxpY2F0aW9uLCBnYXZlX3VwX3JhdGUpLCB5ID0gZ2F2ZV91cF9yYXRlKSkgKw0KICBnZW9tX2NvbCgpICsgDQogIHlsaW0oMCwgMSkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDcwLCBoanVzdCA9IDEpKSArDQogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSBtaW4oZ2F2ZV91cF9yYXRlKSksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsNCiAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdCA9IG1heChnYXZlX3VwX3JhdGUpKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsNCiAgeGxhYigiUmVwbGljYXRpb24iKSArIA0KICB5bGFiKCJHYXZlIHVwIHJhdGUiKQ0KYGBgDQoNCmBgYHtyfQ0KcGhvbmVfY2FsbHNfcmF0ZSAlPiUgDQogIHN1bW1hcmlzZSgNCiAgICBtaW4gPSBtaW4ocGhvbmVfY2FsbHNfcGVyX2hvdXIpLCANCiAgICBtZWFuID0gbWVhbihwaG9uZV9jYWxsc19wZXJfaG91ciksIA0KICAgIG1heCA9IG1heChwaG9uZV9jYWxsc19wZXJfaG91cikNCiAgICApDQpgYGANCg0KDQpgYGB7cn0NCiMgZXZvbHVjYW8gYW8gbG9uZ28gZG8gZGlhIERPIFFVRT8NCmF0dHJpYnV0ZXMuZGYxICU+JSANCiAgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKSAlPiUgDQogIG11dGF0ZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IpICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JSANCiAgcmVmcmFtZSh0aW1lX3JlamVjdGVkID0gcGVzc29hX2VuZF90aW1lLzYwLCBzdW1jdW1fcGVyX2RheSA9IGN1bXN1bSh2YWx1ZSksIGRheSA9IGRheSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3JlamVjdGVkLCB5ID0gc3VtY3VtX3Blcl9kYXkpKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNCg0KYGBge3J9DQojIGdldCBxdWV1ZSBzaXplIGhpc3RvZ3JhbQ0KcmVzb3VyY2VzICU+JSANCiAgbXV0YXRlKGRheSA9IGNlaWxpbmcodGltZS8oMjQqNjApKSkgJT4lIA0KICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICBtdXRhdGUocmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpKSArDQogIGdlb21fc3RlcChhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArIA0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQpyZXNvdXJjZXMgJT4lIA0KICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUNCiAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lLCB5ID0gcXVldWVfc2l6ZSkpICsNCiAgZ2VvbV9zdGVwKGFlcyhjb2xvciA9IHJlcGxpY2F0aW9uKSwgYWxwaGEgPSAwLjMpICsgDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iKSArDQogIGZhY2V0X3dyYXAodmFycyhkYXkpLCBzY2FsZSA9ICJmcmVlIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeWxpbSgwLDI1MCkNCiAgDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBqb2luIHRoZXNlIDIgcGxvdHMNCmF0dHJpYnV0ZXMuZGYxICU+JSANCiAgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKSAlPiUgDQogIG11dGF0ZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IpICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JSANCiAgcmVmcmFtZSh0aW1lX3JlamVjdGVkID0gcGVzc29hX2VuZF90aW1lLzYwLCBzdW1jdW1fcGVyX2RheSA9IGN1bXN1bSh2YWx1ZSksIGRheSA9IGRheSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3JlamVjdGVkLCB5ID0gc3VtY3VtX3Blcl9kYXkpKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIGdlb21fc3RlcChkYXRhID0gcmVzb3VyY2VzICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKGRheSA9IGNlaWxpbmcodGltZS8oMjQqNjApKSkgJT4lIA0KICAgICAgICAgICAgICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogICAgICAgICAgICAgIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICAgICAgICAgICAgICBtdXRhdGUocmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCkpLCANCiAgICAgICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpLCBhbHBoYSA9IDAuMykgKyANCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IHJlc291cmNlcyAlPiUgDQogICAgICAgICAgICAgICAgbXV0YXRlKGRheSA9IGNlaWxpbmcodGltZS8oMjQqNjApKSkgJT4lIA0KICAgICAgICAgICAgICAgIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUNCiAgICAgICAgICAgICAgICByZWZyYW1lKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHRpbWUgPSB0aW1lLzYwLCBxdWV1ZV9zaXplID0gcXVldWUpICU+JSANCiAgICAgICAgICAgICAgICBtdXRhdGUocmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCkpLCANCiAgICAgICAgICAgICAgYWVzKHggPSB0aW1lLCB5ID0gcXVldWVfc2l6ZSksIG1ldGhvZD0ibG0iKSArDQogIGZhY2V0X3dyYXAodmFycyhkYXkpLCBzY2FsZSA9ICJmcmVlIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeWxpbSgwLDg3NSkNCiAgDQphdHRyaWJ1dGVzLmRmMSAlPiUgDQogIGZpbHRlcihrZXkgPT0gInBob25lX3JlamVjdGVkIikgJT4lIA0KICBtdXRhdGUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgcmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKSAlPiUgDQogIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUgDQogIHJlZnJhbWUodGltZV9yZWplY3RlZCA9IHBlc3NvYV9lbmRfdGltZS82MCwgc3VtY3VtX3Blcl9kYXkgPSBjdW1zdW0odmFsdWUpLCBkYXkgPSBkYXkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdGltZV9yZWplY3RlZCwgeSA9IHN1bWN1bV9wZXJfZGF5KSkgKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gcmVwbGljYXRpb24pLCBhbHBoYSA9IDAuMykgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZmFjZXRfd3JhcCh2YXJzKGRheSksIHNjYWxlID0gImZyZWUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBnZW9tX3N0ZXAoZGF0YSA9IHJlc291cmNlcyAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICAgICAgICAgICAgICByZWZyYW1lKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHRpbWUgPSB0aW1lLzYwLCBxdWV1ZV9zaXplID0gcXVldWUpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICBhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSwgYWxwaGEgPSAwLjMpICsgDQogIGdlb21fc21vb3RoKGRhdGEgPSByZXNvdXJjZXMgJT4lIA0KICAgICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogICAgICAgICAgICAgICAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogICAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpLCBtZXRob2Q9ImxtIikgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQoNCmF0dHJpYnV0ZXMuZGYxICU+JSANCiAgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKSAlPiUgDQogIG11dGF0ZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IpICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JSANCiAgcmVmcmFtZSh0aW1lX3JlamVjdGVkID0gcGVzc29hX2VuZF90aW1lLzYwLCBzdW1jdW1fcGVyX2RheSA9IGN1bXN1bSh2YWx1ZSksIGRheSA9IGRheSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3JlamVjdGVkLCB5ID0gc3VtY3VtX3Blcl9kYXkpKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArDQogIGdlb21fc21vb3RoKCkgKw0KDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBnZW9tX3N0ZXAoZGF0YSA9IHJlc291cmNlcyAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICAgICAgICAgICAgICByZWZyYW1lKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHRpbWUgPSB0aW1lLzYwLCBxdWV1ZV9zaXplID0gcXVldWUpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICBhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSwgYWxwaGEgPSAwLjMpICsgDQogIGdlb21fc21vb3RoKGRhdGEgPSByZXNvdXJjZXMgJT4lIA0KICAgICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogICAgICAgICAgICAgICAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogICAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpLCBtZXRob2Q9ImxtIikgKw0KDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQpgYGB7cn0NCiMgdGVtcG8gZGUgZXNwZXJhIGRhcXVlbGVzIHF1ZSBjb25jbHVpcmFtLCBzcGVhcmRhbyBwb3IgY2hhbWFkYSBlIG1lbnNhZ2VtDQphdHRyaWJ1dGVzICU+JSANCiAgZmlsdGVyKGtleSA9PSAiYXRlbmRpZG8iKSAlPiUgDQogIGxlZnRfam9pbihhcnJpdmFscy5kZjEgJT4lIHNlbGVjdChzdGFydF90aW1lLCBuYW1lLCByZXBsaWNhdGlvbiksIGpvaW5fYnkoIm5hbWUiLCAicmVwbGljYXRpb24iKSkgJT4lIA0KICBsZWZ0X2pvaW4oY29udGFjdG9zLmRmMSwgam9pbl9ieSgibmFtZSIsICJyZXBsaWNhdGlvbiIpKSAlPiUgDQogIG11dGF0ZSh0aW1lX3dhaXRpbmcgPSB0aW1lIC0gc3RhcnRfdGltZSkgJT4lIA0KICBzZWxlY3QobmFtZSwgdmFsdWUsIHJlcGxpY2F0aW9uLCB0aW1lX3dhaXRpbmcsIGNvbnRhY3RvKSAtPiB3YWl0aW5nX3RpbWVzDQp3YWl0aW5nX3RpbWVzDQpgYGANCg0KDQpgYGB7cn0NCiMgaGlzdG9ncmFtYSAobiB0b3UgYSBwZXJjZWJlciBwcSDDqSBxIGjDoSB3YWl0aW5nIHRpbWVzIG1haW9yZXMgcXVlIDUpDQp3YWl0aW5nX3RpbWVzICU+JSANCiAgZmlsdGVyKGNvbnRhY3RvID09ICJ0ZWxlZm9uZSIpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3dhaXRpbmcpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDApICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQp3YWl0aW5nX3RpbWVzICU+JSANCiAgZmlsdGVyKGNvbnRhY3RvID09ICJhcHAiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gdGltZV93YWl0aW5nLzYwKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQoNCmBgYHtyfQ0KIyByZWd1bGFyIHdhaXRpbmcgdGltZSBzdGF0cyB3aXRob3V0IGdyYXBocw0Kd2FpdGluZ190aW1lcyAlPiUgDQogIGdyb3VwX2J5KGNvbnRhY3RvKSAlPiUgDQogIHN1bW1hcmlzZShtZWFuID0gbWVhbih0aW1lX3dhaXRpbmcpLCBzZCA9IHNkKHRpbWVfd2FpdGluZyksIG1lZGlhbiA9IG1lZGlhbih0aW1lX3dhaXRpbmcpKSANCndhaXRpbmdfdGltZXMgJT4lIA0KICBncm91cF9ieShjb250YWN0bykgJT4lIA0KICBzdW1tYXJpc2UocXVhbnRpbGVfMTAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuMSksIHF1YW50aWxlXzIwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjIpLCBxdWFudGlsZV8zMCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC4zKSwgcXVhbnRpbGVfNDAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuNCksIHF1YW50aWxlXzUwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjUpLCBxdWFudGlsZV82MCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC42KSwgcXVhbnRpbGVfNzAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuNyksIHF1YW50aWxlXzgwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjgpLCBxdWFudGlsZV85MCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC45KSwgcXVhbnRpbGVfOTUgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuOTUpLCBxdWFudGlsZV85OSA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC45OSkpDQpgYGANCg0KDQpgYGB7cn0NCnJlc291cmNlcyAlPiUgcGxvdChtZXRyaWMgPSAidXRpbGl6YXRpb24iKQ0KcmVzb3VyY2VzICU+JSBwbG90KG1ldHJpYyA9ICJ1c2FnZSIsIHN0ZXBzID0gVCkNCg0KYGBgDQojIyBQZXJndW50YSAyIA0KDQpBbMOtbmVhcyBjb20gbXVkYW7Dp2FzIHJlbGF0aXZhcyDDoHMgaW50ZXJydXDDp8O1ZXMNCg0KYGBge3J9DQojIG11ZGFuw6dhcw0KZW52cyA8LSBsYXBwbHkoMTo1MCwgZnVuY3Rpb24oaSkgew0KICBlbnYgPDwtIHNpbW1lcigiaG9zcGl0YWwiKSANCiAgZW52ICU+JSANCiAgICBhZGRfZ2VuZXJhdG9yKCJwZXNzb2FfZGVfbWFuaGEiLCBwZXNzb2EsIGZyb21fdG8oOCo2MCwgMTAqNjAsIFwoKSByZXhwKDEsICgxMjAvMikvNjApLCBldmVyeSA9IDI0KjYwLCBhcnJpdmUgPSBGKSwgbW9uID0gMikgJT4lIA0KICAgIGFkZF9nZW5lcmF0b3IoInBlc3NvYV9kZV90YXJkZSIsIHBlc3NvYSwgZnJvbV90bygxMCo2MCwgMTYqNjAsIFwoKSByZXhwKDEsICgyNDAvNikvNjApLCBldmVyeSA9IDI0KjYwLCBhcnJpdmUgPSBGKSwgbW9uID0gMikgJT4lIA0KICAgIGFkZF9nZW5lcmF0b3IoInBlc3NvYV9kZV9ub2l0ZSIsIHBlc3NvYSwgZnJvbV90bygxNio2MCwgMTgqNjAsIFwoKSByZXhwKDEsICg0MC8yKS82MCksIGV2ZXJ5ID0gMjQqNjAsIGFycml2ZSA9IEYpLCBtb24gPSAyKSAlPiUgDQogICAgYWRkX3Jlc291cmNlKCJhZG1pbmlzdHJhZG9yIiwgc2NoZWR1bGUoDQogICAgICBjKDggLCA5ICwgOS41ICwgMTIgLCAxNS41ICwxOCkqNjAsIA0KICAgICAgYyggIDEgLCAyLCAgICA0LCAgIDMsICAgICAxLCAgMCksIA0KICAgICAgcGVyaW9kID0gMjQqNjANCiAgICApKSAlPiUNCiAgICBydW4oMjQqNjAqNSkNCn0pDQoNCmVudnMgJT4lIHNpbW1lci5wbG90OjpnZXRfbW9uX2Fycml2YWxzKG9uZ29pbmcgPSBUKSAtPiBhcnJpdmFscw0KZW52cyAlPiUgc2ltbWVyLnBsb3Q6OmdldF9tb25fYXR0cmlidXRlcygpIC0+IGF0dHJpYnV0ZXMNCmVudnMgJT4lIHNpbW1lci5wbG90OjpnZXRfbW9uX3Jlc291cmNlcygpIC0+IHJlc291cmNlcw0KYGBgDQoNCiMjIDIuYikNCg0KYGBge3J9DQojIGIpDQphcnJpdmFscyAlPiUNCiAgZmlsdGVyKHN0YXJ0X3RpbWUgIT0gLTEpICU+JSAjIG4gc2VpIHBxIGUgcSBpc3RvIGFjb250ZWNlDQogIGFycmFuZ2Uoc3RhcnRfdGltZSkgJT4lIA0KICBzZWxlY3QoLWFjdGl2aXR5X3RpbWUpICU+JSANCiAgbXV0YXRlKA0KICAgIGRheSA9IGNlaWxpbmcoc3RhcnRfdGltZS8oMjQqNjApKSwNCiAgICBzdGFydF90aW1lX2RheSA9IHN0YXJ0X3RpbWUgJSUgKDI0KjYwKSAlPiUgc2Vjb25kc190b19wZXJpb2QoKSwNCiAgICBlbmRfdGltZV9kYXkgPSBlbmRfdGltZSAlJSAoMjQqNjApICU+JSBzZWNvbmRzX3RvX3BlcmlvZCgpLA0KICAgIHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpLA0KICApIC0+IGFycml2YWxzLmRmMmINCmFycml2YWxzLmRmMmIgJT4lIGZpbHRlcihyZXBsaWNhdGlvbiA9PSAxKSAlPiUgbXV0YXRlKHN0YXJ0X3RpbWVfZGF5ID0gc3RhcnRfdGltZV9kYXkgJT4lIHJvdW5kKDMpLCBlbmRfdGltZV9kYXkgPSBlbmRfdGltZV9kYXkgJT4lIHJvdW5kKDMpKQ0KYGBgDQoNCmBgYHtyfQ0KYXJyaXZhbHMuZGYyYiRyZXBsaWNhdGlvbiA8LSBhcy5pbnRlZ2VyKGFzLmNoYXJhY3RlcihhcnJpdmFscy5kZjJiJHJlcGxpY2F0aW9uKSkNCg0KIyB0ZW1wbyBkZSBlc3BlcmEgZGFxdWVsZXMgcXVlIGNvbmNsdWlyYW0sIHNwZWFyZGFvIHBvciBjaGFtYWRhIGUgbWVuc2FnZW0NCmFycml2YWxzLmRmMmIgJT4lIA0KICBmaWx0ZXIoZmluaXNoZWQgPT0gVFJVRSkgJT4lIA0KICBtdXRhdGUod2FpdGluZ190aW1lID0gZW5kX3RpbWUgLSBzdGFydF90aW1lKSAlPiUgDQogIGxlZnRfam9pbihhdHRyaWJ1dGVzICU+JSBmaWx0ZXIoa2V5ID09ICJ0aXBvX2RlX2NvbnRhY3RvIiksIGJ5ID0gYygibmFtZSIsICJyZXBsaWNhdGlvbiIpKSAlPiUgDQogIG11dGF0ZSh0aXBvX2RlX2NvbnRhY3RvID0gaWZlbHNlKHZhbHVlID09IHRpcG9fZGVfY29udGFjdG8uVEVMRUZPTkUsICJ0ZWxlZm9uZSIsICJtZW5zYWdlbSIpICU+JSBhc19mYWN0b3IsIHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcikgJT4lDQogIHNlbGVjdChuYW1lLCByZXBsaWNhdGlvbiwgd2FpdGluZ190aW1lLCB0aXBvX2RlX2NvbnRhY3RvKSAtPiB3YWl0aW5nX3RpbWVzLjJiDQp3YWl0aW5nX3RpbWVzLjJiDQpgYGANCg0KYGBge3J9DQojIGFuYWxpc2UgZGUgZWZpY2llbmNpYQ0KIyBudW1lcm8gZGUgY2hhbWFkYXMgcGVyZGlkYXMNCiMgYWRkIGRheSB0byBhdHJpYnV0ZXMgZnJvbSBhcnJpdmFscy5kZjJiDQphdHRyaWJ1dGVzICU+JSANCiAgbGVmdF9qb2luKGFycml2YWxzLmRmMmIgJT4lIHNlbGVjdChuYW1lLCByZXBsaWNhdGlvbiwgZGF5LCBzdGFydF90aW1lLCBlbmRfdGltZSwgZW5kX3RpbWVfZGF5KSwgYnkgPSBjKCJuYW1lIiwgInJlcGxpY2F0aW9uIikpICU+JSANCiAgcmVuYW1lKGF0cmlidXRlX3RpbWUgPSB0aW1lLCBwZXNzb2Ffc3RhcnRfdGltZSA9IHN0YXJ0X3RpbWUsIHBlc3NvYV9lbmRfdGltZSA9IGVuZF90aW1lKSAlPiUgDQogIHNlbGVjdChhdHJpYnV0ZV90aW1lLCBuYW1lLCBrZXksIHZhbHVlLCByZXBsaWNhdGlvbiwgZGF5LCBwZXNzb2Ffc3RhcnRfdGltZSwgcGVzc29hX2VuZF90aW1lKSAtPiBhdHRyaWJ1dGVzLmRmMmINCmF0dHJpYnV0ZXMuZGYyYiANCmBgYA0KDQpgYGB7cn0NCiMgcGVzc29hcyBlIG9zIHNldXMgdGlwb3MgZGUgY2hhbWFkYQ0KYXR0cmlidXRlcy5kZjJiICU+JSANCiAgZmlsdGVyKGtleSA9PSAidGlwb19kZV9jb250YWN0byIpICU+JSANCiAgc2VsZWN0KG5hbWUsIHJlcGxpY2F0aW9uLCB2YWx1ZSkgJT4lIA0KICBtdXRhdGUodmFsdWUgPSBpZmVsc2UodmFsdWUgPT0gdGlwb19kZV9jb250YWN0by5URUxFRk9ORSwgInRlbGVmb25lIiwgImFwcCIpKSAlPiUNCiAgcmVuYW1lKGNvbnRhY3RvID0gdmFsdWUpIC0+IGNvbnRhY3Rvcy5kZjJiDQpgYGANCg0KDQpgYGB7cn0NCiMgcGVyY2VudGFnZW0gZGUgY2hhbWFkYXMgcGVyZGlkYXMNCmF0dHJpYnV0ZXMuZGYyYiAlPiUgDQogIGZpbHRlcihrZXkgPT0gInRpcG9fZGVfY29udGFjdG8iLCB2YWx1ZSA9PSB0aXBvX2RlX2NvbnRhY3RvLlRFTEVGT05FKSAlPiUgDQogIGxlZnRfam9pbihhdHRyaWJ1dGVzLmRmMmIgJT4lIGZpbHRlcihrZXkgPT0gInBob25lX3JlamVjdGVkIikgJT4lIG11dGF0ZShnYXZlX3VwID0ga2V5KSAlPiUgc2VsZWN0KG5hbWUsIHJlcGxpY2F0aW9uLCBnYXZlX3VwKSwgam9pbl9ieSgibmFtZSIsICJyZXBsaWNhdGlvbiIpKSAlPiUgDQogIG11dGF0ZShnYXZlX3VwID0gaWZfZWxzZShnYXZlX3VwICU+JSBpcy5uYSwgVCwgRiksIHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcikgJT4lIA0KICBzZWxlY3QobmFtZSwgcmVwbGljYXRpb24sIHBlc3NvYV9lbmRfdGltZSwgZ2F2ZV91cCkgLT4gcGhvbmVfY2FsbHMNCnBob25lX2NhbGxzDQpgYGANCg0KYGBge3J9DQojIHBob25lX2NhbGxzIHJhdGUgcGVyIHJlcGxpY2F0aW9uDQpwaG9uZV9jYWxscyAlPiUgDQogIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUgDQogIHN1bW1hcmlzZShwaG9uZV9jYWxscyA9IG4oKSwgZ2F2ZV91cCA9IHN1bShnYXZlX3VwKSkgJT4lIA0KICBtdXRhdGUocGhvbmVfY2FsbHNfcGVyX2hvdXIgPSBwaG9uZV9jYWxscy8xMCwgZ2F2ZV91cF9yYXRlID0gZ2F2ZV91cC9waG9uZV9jYWxscykgJT4lIA0KICBhcnJhbmdlKGRlc2MoZ2F2ZV91cF9yYXRlKSkgLT4gcGhvbmVfY2FsbHNfcmF0ZQ0KcGhvbmVfY2FsbHNfcmF0ZQ0KDQpwaG9uZV9jYWxsc19yYXRlICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihyZXBsaWNhdGlvbiwgZ2F2ZV91cF9yYXRlKSwgeSA9IGdhdmVfdXBfcmF0ZSkpICsNCiAgZ2VvbV9jb2woKSArIA0KICB5bGltKDAsIDEpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA3MCwgaGp1c3QgPSAxKSkgKw0KICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gbWluKGdhdmVfdXBfcmF0ZSkpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiKSArDQogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSBtYXgoZ2F2ZV91cF9yYXRlKSksIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsdWUiKSArDQogIHhsYWIoIlJlcGxpY2F0aW9uIikgKyANCiAgeWxhYigiR2F2ZSB1cCByYXRlIikNCg0KYGBgDQoNCmBgYHtyfQ0KcGhvbmVfY2FsbHNfcmF0ZSAlPiUgDQogIHN1bW1hcmlzZSgNCiAgICBtaW4gPSBtaW4ocGhvbmVfY2FsbHNfcGVyX2hvdXIpLCANCiAgICBtZWFuID0gbWVhbihwaG9uZV9jYWxsc19wZXJfaG91ciksIA0KICAgIG1heCA9IG1heChwaG9uZV9jYWxsc19wZXJfaG91cikNCiAgICApDQpgYGANCg0KDQpgYGB7cn0NCiMgZXZvbHVjYW8gYW8gbG9uZ28gZG8gZGlhIERPIFFVRT8NCmF0dHJpYnV0ZXMuZGYyYiAlPiUgDQogIGZpbHRlcihrZXkgPT0gInBob25lX3JlamVjdGVkIikgJT4lIA0KICBtdXRhdGUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgcmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKSAlPiUgDQogIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUgDQogIHJlZnJhbWUodGltZV9yZWplY3RlZCA9IHBlc3NvYV9lbmRfdGltZS82MCwgc3VtY3VtX3Blcl9kYXkgPSBjdW1zdW0odmFsdWUpLCBkYXkgPSBkYXkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdGltZV9yZWplY3RlZCwgeSA9IHN1bWN1bV9wZXJfZGF5KSkgKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gcmVwbGljYXRpb24pLCBhbHBoYSA9IDAuMykgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZmFjZXRfd3JhcCh2YXJzKGRheSksIHNjYWxlID0gImZyZWUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQoNCmBgYHtyfQ0KIyBnZXQgcXVldWUgc2l6ZSBoaXN0b2dyYW0NCnJlc291cmNlcyAlPiUgDQogIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICByZWZyYW1lKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHRpbWUgPSB0aW1lLzYwLCBxdWV1ZV9zaXplID0gcXVldWUpICU+JSANCiAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSkgKw0KICBnZW9tX3N0ZXAoYWVzKGNvbG9yID0gcmVwbGljYXRpb24pLCBhbHBoYSA9IDAuMykgKyANCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIpICsNCiAgZmFjZXRfd3JhcCh2YXJzKGRheSksIHNjYWxlID0gImZyZWUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KcmVzb3VyY2VzICU+JSANCiAgbXV0YXRlKGRheSA9IGNlaWxpbmcodGltZS8oMjQqNjApKSkgJT4lIA0KICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICBtdXRhdGUocmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpKSArDQogIGdlb21fc3RlcChhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArIA0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHlsaW0oMCwyNTApDQogIA0KYGBgDQoNCg0KYGBge3J9DQojIGpvaW4gdGhlc2UgMiBwbG90cw0KYXR0cmlidXRlcy5kZjJiICU+JSANCiAgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKSAlPiUgDQogIG11dGF0ZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IpICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JSANCiAgcmVmcmFtZSh0aW1lX3JlamVjdGVkID0gcGVzc29hX2VuZF90aW1lLzYwLCBzdW1jdW1fcGVyX2RheSA9IGN1bXN1bSh2YWx1ZSksIGRheSA9IGRheSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3JlamVjdGVkLCB5ID0gc3VtY3VtX3Blcl9kYXkpKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIGdlb21fc3RlcChkYXRhID0gcmVzb3VyY2VzICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKGRheSA9IGNlaWxpbmcodGltZS8oMjQqNjApKSkgJT4lIA0KICAgICAgICAgICAgICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogICAgICAgICAgICAgIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICAgICAgICAgICAgICBtdXRhdGUocmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCkpLCANCiAgICAgICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpLCBhbHBoYSA9IDAuMykgKyANCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IHJlc291cmNlcyAlPiUgDQogICAgICAgICAgICAgICAgbXV0YXRlKGRheSA9IGNlaWxpbmcodGltZS8oMjQqNjApKSkgJT4lIA0KICAgICAgICAgICAgICAgIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUNCiAgICAgICAgICAgICAgICByZWZyYW1lKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHRpbWUgPSB0aW1lLzYwLCBxdWV1ZV9zaXplID0gcXVldWUpICU+JSANCiAgICAgICAgICAgICAgICBtdXRhdGUocmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCkpLCANCiAgICAgICAgICAgICAgYWVzKHggPSB0aW1lLCB5ID0gcXVldWVfc2l6ZSksIG1ldGhvZD0ibG0iKSArDQogIGZhY2V0X3dyYXAodmFycyhkYXkpLCBzY2FsZSA9ICJmcmVlIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeWxpbSgwLDg3NSkNCiAgDQphdHRyaWJ1dGVzLmRmMmIgJT4lIA0KICBmaWx0ZXIoa2V5ID09ICJwaG9uZV9yZWplY3RlZCIpICU+JSANCiAgbXV0YXRlKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcikgJT4lIA0KICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lIA0KICByZWZyYW1lKHRpbWVfcmVqZWN0ZWQgPSBwZXNzb2FfZW5kX3RpbWUvNjAsIHN1bWN1bV9wZXJfZGF5ID0gY3Vtc3VtKHZhbHVlKSwgZGF5ID0gZGF5KSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHRpbWVfcmVqZWN0ZWQsIHkgPSBzdW1jdW1fcGVyX2RheSkpICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IHJlcGxpY2F0aW9uKSwgYWxwaGEgPSAwLjMpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGZhY2V0X3dyYXAodmFycyhkYXkpLCBzY2FsZSA9ICJmcmVlIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgZ2VvbV9zdGVwKGRhdGEgPSByZXNvdXJjZXMgJT4lIA0KICAgICAgICAgICAgICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogICAgICAgICAgICAgIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUNCiAgICAgICAgICAgICAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSksIA0KICAgICAgICAgICAgYWVzKHggPSB0aW1lLCB5ID0gcXVldWVfc2l6ZSksIGFscGhhID0gMC4zKSArIA0KICBnZW9tX3Ntb290aChkYXRhID0gcmVzb3VyY2VzICU+JSANCiAgICAgICAgICAgICAgICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogICAgICAgICAgICAgICAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICAgICAgICAgICAgICAgIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICAgICAgICAgICAgICAgIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSksIA0KICAgICAgICAgICAgICBhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSwgbWV0aG9kPSJsbSIpICsNCiAgZmFjZXRfd3JhcCh2YXJzKGRheSksIHNjYWxlID0gImZyZWUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBnZ3RpdGxlKCJUYW1hbmhvIGRhIGZpbGEgZSBjaGFtYWRhcyByZWplaXRhZGFzIDJiKSBwb3IgZGlhIikgKw0KICB4bGFiKCJUZW1wbyBkZSBzaW11bGHDp8OjbyAoaG9yYXMpIikgKyANCiAgeWxhYigiTsO6bWVybyBkZSBjaGFtYWRhcyByZWplaXRhZGFzIikNCg0KDQoNCmF0dHJpYnV0ZXMuZGYyYiAlPiUgDQogIGZpbHRlcihrZXkgPT0gInBob25lX3JlamVjdGVkIikgJT4lIA0KICBtdXRhdGUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgcmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKSAlPiUgDQogIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUgDQogIHJlZnJhbWUodGltZV9yZWplY3RlZCA9IHBlc3NvYV9lbmRfdGltZS82MCwgc3VtY3VtX3Blcl9kYXkgPSBjdW1zdW0odmFsdWUpLCBkYXkgPSBkYXkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdGltZV9yZWplY3RlZCwgeSA9IHN1bWN1bV9wZXJfZGF5KSkgKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gcmVwbGljYXRpb24pLCBhbHBoYSA9IDAuMykgKw0KICBnZW9tX3Ntb290aCgpICsNCg0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgZ2VvbV9zdGVwKGRhdGEgPSByZXNvdXJjZXMgJT4lIA0KICAgICAgICAgICAgICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogICAgICAgICAgICAgIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUNCiAgICAgICAgICAgICAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSksIA0KICAgICAgICAgICAgYWVzKHggPSB0aW1lLCB5ID0gcXVldWVfc2l6ZSksIGFscGhhID0gMC4zKSArIA0KICBnZW9tX3Ntb290aChkYXRhID0gcmVzb3VyY2VzICU+JSANCiAgICAgICAgICAgICAgICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogICAgICAgICAgICAgICAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICAgICAgICAgICAgICAgIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICAgICAgICAgICAgICAgIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSksIA0KICAgICAgICAgICAgICBhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSwgbWV0aG9kPSJsbSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIGdndGl0bGUoIlRhbWFuaG8gZGEgZmlsYSBlIGNoYW1hZGFzIHJlamVpdGFkYXMgMmIpIG5vcyA1IGRpYXMiKSArDQogIHhsYWIoIlRlbXBvIGRlIHNpbXVsYcOnw6NvIChob3JhcykiKSArIA0KICB5bGFiKCJOw7ptZXJvIGRlIGNoYW1hZGFzIHJlamVpdGFkYXMiKQ0KDQpgYGANCg0KYGBge3J9DQojIHRlbXBvIGRlIGVzcGVyYSBkYXF1ZWxlcyBxdWUgY29uY2x1aXJhbSwgc3BlYXJkYW8gcG9yIGNoYW1hZGEgZSBtZW5zYWdlbQ0KYXR0cmlidXRlcyAlPiUgDQogIGZpbHRlcihrZXkgPT0gImF0ZW5kaWRvIikgJT4lIA0KICBsZWZ0X2pvaW4oYXJyaXZhbHMuZGYyYiAlPiUgc2VsZWN0KHN0YXJ0X3RpbWUsIG5hbWUsIHJlcGxpY2F0aW9uKSwgam9pbl9ieSgibmFtZSIsICJyZXBsaWNhdGlvbiIpKSAlPiUgDQogIGxlZnRfam9pbihjb250YWN0b3MuZGYyYiwgam9pbl9ieSgibmFtZSIsICJyZXBsaWNhdGlvbiIpKSAlPiUgDQogIG11dGF0ZSh0aW1lX3dhaXRpbmcgPSB0aW1lIC0gc3RhcnRfdGltZSkgJT4lIA0KICBzZWxlY3QobmFtZSwgdmFsdWUsIHJlcGxpY2F0aW9uLCB0aW1lX3dhaXRpbmcsIGNvbnRhY3RvKSAtPiB3YWl0aW5nX3RpbWVzDQp3YWl0aW5nX3RpbWVzDQpgYGANCg0KDQpgYGB7cn0NCiMgaGlzdG9ncmFtYSAobiB0b3UgYSBwZXJjZWJlciBwcSDDqSBxIGjDoSB3YWl0aW5nIHRpbWVzIG1haW9yZXMgcXVlIDUpDQp3YWl0aW5nX3RpbWVzICU+JSANCiAgZmlsdGVyKGNvbnRhY3RvID09ICJ0ZWxlZm9uZSIpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3dhaXRpbmcpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDApICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSsNCiAgeWxpbSgwLCAxMDAwKQ0KDQp3YWl0aW5nX3RpbWVzICU+JSANCiAgZmlsdGVyKGNvbnRhY3RvID09ICJhcHAiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gdGltZV93YWl0aW5nLzYwKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQoNCmBgYHtyfQ0KIyByZWd1bGFyIHdhaXRpbmcgdGltZSBzdGF0cyB3aXRob3V0IGdyYXBocw0Kd2FpdGluZ190aW1lcyAlPiUgDQogIGdyb3VwX2J5KGNvbnRhY3RvKSAlPiUgDQogIHN1bW1hcmlzZShtZWFuID0gbWVhbih0aW1lX3dhaXRpbmcpLCBzZCA9IHNkKHRpbWVfd2FpdGluZyksIG1lZGlhbiA9IG1lZGlhbih0aW1lX3dhaXRpbmcpKSANCndhaXRpbmdfdGltZXMgJT4lIA0KICBncm91cF9ieShjb250YWN0bykgJT4lIA0KICBzdW1tYXJpc2UocXVhbnRpbGVfMTAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuMSksIHF1YW50aWxlXzIwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjIpLCBxdWFudGlsZV8zMCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC4zKSwgcXVhbnRpbGVfNDAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuNCksIHF1YW50aWxlXzUwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjUpLCBxdWFudGlsZV82MCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC42KSwgcXVhbnRpbGVfNzAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuNyksIHF1YW50aWxlXzgwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjgpLCBxdWFudGlsZV85MCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC45KSwgcXVhbnRpbGVfOTUgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuOTUpLCBxdWFudGlsZV85OSA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC45OSkpDQpgYGANCg0KDQpgYGB7cn0NCnJlc291cmNlcyAlPiUgcGxvdChtZXRyaWMgPSAidXRpbGl6YXRpb24iKQ0KcmVzb3VyY2VzICU+JSBwbG90KG1ldHJpYyA9ICJ1c2FnZSIsIHN0ZXBzID0gVCkNCmBgYA0KDQojIyAyLmEpIC0gLSAtIC0gLSAtIC0gLSAtIC0gLSAtDQoNCmBgYHtyfQ0KIyBhKSBzZXRfcHJpb3JpdGl6YXRpb24oXCgpIGlmIChnZXRfYXR0cmlidXRlKGVudiwgInRpcG9fZGVfY29uc3VsdGEiKSA9PSB0aXBvX2RlX2NvbnN1bHRhLlRFTEVGT05JQ0EpIGMoMSwgLTEsIEZBTFNFKSBlbHNlIGMoMCAsIC0xLCBGQUxTRSkpDQpwZXNzb2FfXyA8LSBqb2luKA0KICBwZXNzb2FbMToyXSwgDQogIHRyYWplY3RvcnkoImNoYW5nZV9wcmlvIikgJT4lIA0KICAgICBzZXRfcHJpb3JpdGl6YXRpb24oXCgpIGlmIChnZXRfYXR0cmlidXRlKGVudiwgInRpcG9fZGVfY29udGFjdG8iKSA9PSB0aXBvX2RlX2NvbnRhY3RvLlRFTEVGT05FKSBjKDEsIDEsIEZBTFNFKSBlbHNlIGMoMCAsIDAsIEZBTFNFKSksDQogIHBlc3NvYVstKDE6MyldDQopDQpwZXNzb2FfXw0KYGBgDQoNCg0KYGBge3J9DQplbnZzIDwtIGxhcHBseSgxOjUwLCBmdW5jdGlvbihpKSB7DQogIGVudiA8PC0gc2ltbWVyKCJob3NwaXRhbCIpIA0KICBlbnYgJT4lIA0KICAgIGFkZF9nZW5lcmF0b3IoInBlc3NvYV9kZV9tYW5oYSIsIHBlc3NvYV9fLCBmcm9tX3RvKDgqNjAsIDEwKjYwLCBcKCkgcmV4cCgxLCAoMTIwLzIpLzYwKSwgZXZlcnkgPSAyNCo2MCwgYXJyaXZlID0gRiksIG1vbiA9IDIpICU+JSANCiAgICBhZGRfZ2VuZXJhdG9yKCJwZXNzb2FfZGVfdGFyZGUiLCBwZXNzb2FfXywgZnJvbV90bygxMCo2MCwgMTYqNjAsIFwoKSByZXhwKDEsICgyNDAvNikvNjApLCBldmVyeSA9IDI0KjYwLCBhcnJpdmUgPSBGKSwgbW9uID0gMikgJT4lIA0KICAgIGFkZF9nZW5lcmF0b3IoInBlc3NvYV9kZV9ub2l0ZSIsIHBlc3NvYV9fLCBmcm9tX3RvKDE2KjYwLCAxOCo2MCwgXCgpIHJleHAoMSwgKDQwLzIpLzYwKSwgZXZlcnkgPSAyNCo2MCwgYXJyaXZlID0gRiksIG1vbiA9IDIpICU+JSANCiAgICBhZGRfcmVzb3VyY2UoImFkbWluaXN0cmFkb3IiLCBzY2hlZHVsZSgNCiAgICAgIGMoOCAsIDkgLCA5LjUgLCAxMiAsIDE1LjUgLDE4KSo2MCwgDQogICAgICBjKCAgMSAsIDIsICAgIDQsICAgMywgICAgIDEsICAwKSwgDQogICAgICBwZXJpb2QgPSAyNCo2MA0KICAgICkpICU+JQ0KICAgIHJ1bigyNCo2MCo1KQ0KfSkNCg0KZW52cyAlPiUgc2ltbWVyLnBsb3Q6OmdldF9tb25fYXJyaXZhbHMob25nb2luZyA9IFQpIC0+IGFycml2YWxzDQplbnZzICU+JSBzaW1tZXIucGxvdDo6Z2V0X21vbl9hdHRyaWJ1dGVzKCkgLT4gYXR0cmlidXRlcw0KZW52cyAlPiUgc2ltbWVyLnBsb3Q6OmdldF9tb25fcmVzb3VyY2VzKCkgLT4gcmVzb3VyY2VzDQpgYGANCg0KYGBge3J9DQphcnJpdmFscyAlPiUNCiAgZmlsdGVyKHN0YXJ0X3RpbWUgIT0gLTEpICU+JSAjIG4gc2VpIHBxIGUgcSBpc3RvIGFjb250ZWNlDQogIGFycmFuZ2Uoc3RhcnRfdGltZSkgJT4lIA0KICBzZWxlY3QoLWFjdGl2aXR5X3RpbWUpICU+JSANCiAgbXV0YXRlKA0KICAgIGRheSA9IGNlaWxpbmcoc3RhcnRfdGltZS8oMjQqNjApKSwNCiAgICBzdGFydF90aW1lX2RheSA9IHN0YXJ0X3RpbWUgJSUgKDI0KjYwKSAlPiUgc2Vjb25kc190b19wZXJpb2QoKSwNCiAgICBlbmRfdGltZV9kYXkgPSBlbmRfdGltZSAlJSAoMjQqNjApICU+JSBzZWNvbmRzX3RvX3BlcmlvZCgpLA0KICAgIHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpLA0KICApIC0+IGFycml2YWxzLmRmMmENCmFycml2YWxzLmRmMmEgJT4lIGZpbHRlcihyZXBsaWNhdGlvbiA9PSAxKSAlPiUgbXV0YXRlKHN0YXJ0X3RpbWVfZGF5ID0gc3RhcnRfdGltZV9kYXkgJT4lIHJvdW5kKDMpLCBlbmRfdGltZV9kYXkgPSBlbmRfdGltZV9kYXkgJT4lIHJvdW5kKDMpKSANCmBgYA0KDQpgYGB7cn0NCmFycml2YWxzLmRmMmEkcmVwbGljYXRpb24gPC0gYXMuaW50ZWdlcihhcy5jaGFyYWN0ZXIoYXJyaXZhbHMuZGYyYSRyZXBsaWNhdGlvbikpDQoNCiMgYW5hbGlzZSBkZSBlZmljaWVuY2lhDQphdHRyaWJ1dGVzICU+JSANCiAgbGVmdF9qb2luKGFycml2YWxzLmRmMmEsIGJ5ID0gYygibmFtZSIsICJyZXBsaWNhdGlvbiIpICkgJT4lIA0KICBzZWxlY3QobmFtZSwga2V5LCB2YWx1ZSwgcmVwbGljYXRpb24sIGRheSwgZW5kX3RpbWUsIGVuZF90aW1lX2RheSkgLT4gYXRyaWJ1dGVzLmRmMmENCmF0cmlidXRlcy5kZjJhICU+JSANCiAgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBhbmFsaXNlIGRlIGVmaWNpZW5jaWENCiMgbnVtZXJvIGRlIGNoYW1hZGFzIHBlcmRpZGFzDQojIGFkZCBkYXkgdG8gYXRyaWJ1dGVzIGZyb20gYXJyaXZhbHMuZGYyYQ0KYXR0cmlidXRlcyAlPiUgDQogIGxlZnRfam9pbihhcnJpdmFscy5kZjJhICU+JSBzZWxlY3QobmFtZSwgcmVwbGljYXRpb24sIGRheSwgc3RhcnRfdGltZSwgZW5kX3RpbWUsIGVuZF90aW1lX2RheSksIGJ5ID0gYygibmFtZSIsICJyZXBsaWNhdGlvbiIpKSAlPiUgDQogIHJlbmFtZShhdHJpYnV0ZV90aW1lID0gdGltZSwgcGVzc29hX3N0YXJ0X3RpbWUgPSBzdGFydF90aW1lLCBwZXNzb2FfZW5kX3RpbWUgPSBlbmRfdGltZSkgJT4lIA0KICBzZWxlY3QoYXRyaWJ1dGVfdGltZSwgbmFtZSwga2V5LCB2YWx1ZSwgcmVwbGljYXRpb24sIGRheSwgcGVzc29hX3N0YXJ0X3RpbWUsIHBlc3NvYV9lbmRfdGltZSkgLT4gYXR0cmlidXRlcy5kZjJhDQphdHRyaWJ1dGVzLmRmMmEgDQpgYGANCg0KYGBge3J9DQojIHBlc3NvYXMgZSBvcyBzZXVzIHRpcG9zIGRlIGNoYW1hZGENCmF0dHJpYnV0ZXMuZGYyYSAlPiUgDQogIGZpbHRlcihrZXkgPT0gInRpcG9fZGVfY29udGFjdG8iKSAlPiUgDQogIHNlbGVjdChuYW1lLCByZXBsaWNhdGlvbiwgdmFsdWUpICU+JSANCiAgbXV0YXRlKHZhbHVlID0gaWZlbHNlKHZhbHVlID09IHRpcG9fZGVfY29udGFjdG8uVEVMRUZPTkUsICJ0ZWxlZm9uZSIsICJhcHAiKSkgJT4lDQogIHJlbmFtZShjb250YWN0byA9IHZhbHVlKSAtPiBjb250YWN0b3MuZGYyYQ0KYGBgDQoNCg0KYGBge3J9DQojIHBlcmNlbnRhZ2VtIGRlIGNoYW1hZGFzIHBlcmRpZGFzDQphdHRyaWJ1dGVzLmRmMmEgJT4lIA0KICBmaWx0ZXIoa2V5ID09ICJ0aXBvX2RlX2NvbnRhY3RvIiwgdmFsdWUgPT0gdGlwb19kZV9jb250YWN0by5URUxFRk9ORSkgJT4lIA0KICBsZWZ0X2pvaW4oYXR0cmlidXRlcy5kZjJhICU+JSBmaWx0ZXIoa2V5ID09ICJwaG9uZV9yZWplY3RlZCIpICU+JSBtdXRhdGUoZ2F2ZV91cCA9IGtleSkgJT4lIHNlbGVjdChuYW1lLCByZXBsaWNhdGlvbiwgZ2F2ZV91cCksIGpvaW5fYnkoIm5hbWUiLCAicmVwbGljYXRpb24iKSkgJT4lIA0KICBtdXRhdGUoZ2F2ZV91cCA9IGlmX2Vsc2UoZ2F2ZV91cCAlPiUgaXMubmEsIFQsIEYpLCByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IpICU+JSANCiAgc2VsZWN0KG5hbWUsIHJlcGxpY2F0aW9uLCBwZXNzb2FfZW5kX3RpbWUsIGdhdmVfdXApIC0+IHBob25lX2NhbGxzDQpwaG9uZV9jYWxscw0KYGBgDQoNCmBgYHtyfQ0KIyBwaG9uZV9jYWxscyByYXRlIHBlciByZXBsaWNhdGlvbg0KcGhvbmVfY2FsbHMgJT4lIA0KICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lIA0KICBzdW1tYXJpc2UocGhvbmVfY2FsbHMgPSBuKCksIGdhdmVfdXAgPSBzdW0oZ2F2ZV91cCkpICU+JSANCiAgbXV0YXRlKHBob25lX2NhbGxzX3Blcl9ob3VyID0gcGhvbmVfY2FsbHMvMTAsIGdhdmVfdXBfcmF0ZSA9IGdhdmVfdXAvcGhvbmVfY2FsbHMpICU+JSANCiAgYXJyYW5nZShkZXNjKGdhdmVfdXBfcmF0ZSkpIC0+IHBob25lX2NhbGxzX3JhdGUNCnBob25lX2NhbGxzX3JhdGUNCg0KcGhvbmVfY2FsbHNfcmF0ZSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIocmVwbGljYXRpb24sIGdhdmVfdXBfcmF0ZSksIHkgPSBnYXZlX3VwX3JhdGUpKSArDQogIGdlb21fY29sKCkgKyANCiAgeWxpbSgwLCAxKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNzAsIGhqdXN0ID0gMSkpICsNCiAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdCA9IG1pbihnYXZlX3VwX3JhdGUpKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAicmVkIikgKw0KICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gbWF4KGdhdmVfdXBfcmF0ZSkpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibHVlIikgKw0KICB4bGFiKCJSZXBsaWNhdGlvbiIpICsgDQogIHlsYWIoIkdhdmUgdXAgcmF0ZSIpDQpgYGANCg0KYGBge3J9DQpwaG9uZV9jYWxsc19yYXRlICU+JSANCiAgc3VtbWFyaXNlKA0KICAgIG1pbiA9IG1pbihwaG9uZV9jYWxsc19wZXJfaG91ciksIA0KICAgIG1lYW4gPSBtZWFuKHBob25lX2NhbGxzX3Blcl9ob3VyKSwgDQogICAgbWF4ID0gbWF4KHBob25lX2NhbGxzX3Blcl9ob3VyKQ0KICAgICkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBldm9sdWNhbyBhbyBsb25nbyBkbyBkaWEgRE8gUVVFPw0KYXR0cmlidXRlcy5kZjJhICU+JSANCiAgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKSAlPiUgDQogIG11dGF0ZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IpICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JSANCiAgcmVmcmFtZSh0aW1lX3JlamVjdGVkID0gcGVzc29hX2VuZF90aW1lLzYwLCBzdW1jdW1fcGVyX2RheSA9IGN1bXN1bSh2YWx1ZSksIGRheSA9IGRheSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3JlamVjdGVkLCB5ID0gc3VtY3VtX3Blcl9kYXkpKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNCg0KYGBge3J9DQojIGdldCBxdWV1ZSBzaXplIGhpc3RvZ3JhbQ0KcmVzb3VyY2VzICU+JSANCiAgbXV0YXRlKGRheSA9IGNlaWxpbmcodGltZS8oMjQqNjApKSkgJT4lIA0KICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICBtdXRhdGUocmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKCkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpKSArDQogIGdlb21fc3RlcChhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArIA0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQpyZXNvdXJjZXMgJT4lIA0KICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUNCiAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lLCB5ID0gcXVldWVfc2l6ZSkpICsNCiAgZ2VvbV9zdGVwKGFlcyhjb2xvciA9IHJlcGxpY2F0aW9uKSwgYWxwaGEgPSAwLjMpICsgDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iKSArDQogIGZhY2V0X3dyYXAodmFycyhkYXkpLCBzY2FsZSA9ICJmcmVlIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeWxpbSgwLDI1MCkNCiAgDQpgYGANCg0KDQpgYGB7cn0NCiMgam9pbiB0aGVzZSAyIHBsb3RzDQphdHRyaWJ1dGVzLmRmMmEgJT4lIA0KICBmaWx0ZXIoa2V5ID09ICJwaG9uZV9yZWplY3RlZCIpICU+JSANCiAgbXV0YXRlKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcikgJT4lIA0KICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lIA0KICByZWZyYW1lKHRpbWVfcmVqZWN0ZWQgPSBwZXNzb2FfZW5kX3RpbWUvNjAsIHN1bWN1bV9wZXJfZGF5ID0gY3Vtc3VtKHZhbHVlKSwgZGF5ID0gZGF5KSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHRpbWVfcmVqZWN0ZWQsIHkgPSBzdW1jdW1fcGVyX2RheSkpICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IHJlcGxpY2F0aW9uKSwgYWxwaGEgPSAwLjMpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGZhY2V0X3dyYXAodmFycyhkYXkpLCBzY2FsZSA9ICJmcmVlIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgZ2VvbV9zdGVwKGRhdGEgPSByZXNvdXJjZXMgJT4lIA0KICAgICAgICAgICAgICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogICAgICAgICAgICAgIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUNCiAgICAgICAgICAgICAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSksIA0KICAgICAgICAgICAgYWVzKHggPSB0aW1lLCB5ID0gcXVldWVfc2l6ZSksIGFscGhhID0gMC4zKSArIA0KICBnZW9tX3Ntb290aChkYXRhID0gcmVzb3VyY2VzICU+JSANCiAgICAgICAgICAgICAgICBtdXRhdGUoZGF5ID0gY2VpbGluZyh0aW1lLygyNCo2MCkpKSAlPiUgDQogICAgICAgICAgICAgICAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICAgICAgICAgICAgICAgIHJlZnJhbWUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgdGltZSA9IHRpbWUvNjAsIHF1ZXVlX3NpemUgPSBxdWV1ZSkgJT4lIA0KICAgICAgICAgICAgICAgIG11dGF0ZShyZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IoKSksIA0KICAgICAgICAgICAgICBhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSwgbWV0aG9kPSJsbSIpICsNCiAgZmFjZXRfd3JhcCh2YXJzKGRheSksIHNjYWxlID0gImZyZWUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICB5bGltKDAsODc1KQ0KICANCmF0dHJpYnV0ZXMuZGYyYSAlPiUgDQogIGZpbHRlcihrZXkgPT0gInBob25lX3JlamVjdGVkIikgJT4lIA0KICBtdXRhdGUoZGF5ID0gZGF5ICU+JSBhc19mYWN0b3IoKSwgcmVwbGljYXRpb24gPSByZXBsaWNhdGlvbiAlPiUgYXNfZmFjdG9yKSAlPiUgDQogIGdyb3VwX2J5KHJlcGxpY2F0aW9uKSAlPiUgDQogIHJlZnJhbWUodGltZV9yZWplY3RlZCA9IHBlc3NvYV9lbmRfdGltZS82MCwgc3VtY3VtX3Blcl9kYXkgPSBjdW1zdW0odmFsdWUpLCBkYXkgPSBkYXkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdGltZV9yZWplY3RlZCwgeSA9IHN1bWN1bV9wZXJfZGF5KSkgKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gcmVwbGljYXRpb24pLCBhbHBoYSA9IDAuMykgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZmFjZXRfd3JhcCh2YXJzKGRheSksIHNjYWxlID0gImZyZWUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBnZW9tX3N0ZXAoZGF0YSA9IHJlc291cmNlcyAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICAgICAgICAgICAgICByZWZyYW1lKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHRpbWUgPSB0aW1lLzYwLCBxdWV1ZV9zaXplID0gcXVldWUpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICBhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSwgYWxwaGEgPSAwLjMpICsgDQogIGdlb21fc21vb3RoKGRhdGEgPSByZXNvdXJjZXMgJT4lIA0KICAgICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogICAgICAgICAgICAgICAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogICAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpLCBtZXRob2Q9ImxtIikgKw0KICBmYWNldF93cmFwKHZhcnMoZGF5KSwgc2NhbGUgPSAiZnJlZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIGdndGl0bGUoIlRhbWFuaG8gZGEgZmlsYSBlIGNoYW1hZGFzIHJlamVpdGFkYXMgMmEpIHBvciBkaWEiKSArDQogIHhsYWIoIlRlbXBvIGRlIHNpbXVsYcOnw6NvIChob3JhcykiKSArIA0KICB5bGFiKCJOw7ptZXJvIGRlIGNoYW1hZGFzIHJlamVpdGFkYXMiKQ0KDQoNCg0KYXR0cmlidXRlcy5kZjJhICU+JSANCiAgZmlsdGVyKGtleSA9PSAicGhvbmVfcmVqZWN0ZWQiKSAlPiUgDQogIG11dGF0ZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCByZXBsaWNhdGlvbiA9IHJlcGxpY2F0aW9uICU+JSBhc19mYWN0b3IpICU+JSANCiAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JSANCiAgcmVmcmFtZSh0aW1lX3JlamVjdGVkID0gcGVzc29hX2VuZF90aW1lLzYwLCBzdW1jdW1fcGVyX2RheSA9IGN1bXN1bSh2YWx1ZSksIGRheSA9IGRheSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB0aW1lX3JlamVjdGVkLCB5ID0gc3VtY3VtX3Blcl9kYXkpKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSByZXBsaWNhdGlvbiksIGFscGhhID0gMC4zKSArDQogIGdlb21fc21vb3RoKCkgKw0KDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBnZW9tX3N0ZXAoZGF0YSA9IHJlc291cmNlcyAlPiUgDQogICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgZ3JvdXBfYnkocmVwbGljYXRpb24pICU+JQ0KICAgICAgICAgICAgICByZWZyYW1lKGRheSA9IGRheSAlPiUgYXNfZmFjdG9yKCksIHRpbWUgPSB0aW1lLzYwLCBxdWV1ZV9zaXplID0gcXVldWUpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICBhZXMoeCA9IHRpbWUsIHkgPSBxdWV1ZV9zaXplKSwgYWxwaGEgPSAwLjMpICsgDQogIGdlb21fc21vb3RoKGRhdGEgPSByZXNvdXJjZXMgJT4lIA0KICAgICAgICAgICAgICAgIG11dGF0ZShkYXkgPSBjZWlsaW5nKHRpbWUvKDI0KjYwKSkpICU+JSANCiAgICAgICAgICAgICAgICBncm91cF9ieShyZXBsaWNhdGlvbikgJT4lDQogICAgICAgICAgICAgICAgcmVmcmFtZShkYXkgPSBkYXkgJT4lIGFzX2ZhY3RvcigpLCB0aW1lID0gdGltZS82MCwgcXVldWVfc2l6ZSA9IHF1ZXVlKSAlPiUgDQogICAgICAgICAgICAgICAgbXV0YXRlKHJlcGxpY2F0aW9uID0gcmVwbGljYXRpb24gJT4lIGFzX2ZhY3RvcigpKSwgDQogICAgICAgICAgICAgIGFlcyh4ID0gdGltZSwgeSA9IHF1ZXVlX3NpemUpLCBtZXRob2Q9ImxtIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgZ2d0aXRsZSgiVGFtYW5obyBkYSBmaWxhIGUgY2hhbWFkYXMgcmVqZWl0YWRhcyAyYSkgbm9zIDUgZGlhcyIpICsNCiAgeGxhYigiVGVtcG8gZGUgc2ltdWxhw6fDo28gKGhvcmFzKSIpICsgDQogIHlsYWIoIk7Dum1lcm8gZGUgY2hhbWFkYXMgcmVqZWl0YWRhcyIpDQoNCmBgYA0KDQpgYGB7cn0NCiMgdGVtcG8gZGUgZXNwZXJhIGRhcXVlbGVzIHF1ZSBjb25jbHVpcmFtLCBzcGVhcmRhbyBwb3IgY2hhbWFkYSBlIG1lbnNhZ2VtDQphdHRyaWJ1dGVzICU+JSANCiAgZmlsdGVyKGtleSA9PSAiYXRlbmRpZG8iKSAlPiUgDQogIGxlZnRfam9pbihhcnJpdmFscy5kZjJhICU+JSBzZWxlY3Qoc3RhcnRfdGltZSwgbmFtZSwgcmVwbGljYXRpb24pLCBqb2luX2J5KCJuYW1lIiwgInJlcGxpY2F0aW9uIikpICU+JSANCiAgbGVmdF9qb2luKGNvbnRhY3Rvcy5kZjJhLCBqb2luX2J5KCJuYW1lIiwgInJlcGxpY2F0aW9uIikpICU+JSANCiAgbXV0YXRlKHRpbWVfd2FpdGluZyA9IHRpbWUgLSBzdGFydF90aW1lKSAlPiUgDQogIHNlbGVjdChuYW1lLCB2YWx1ZSwgcmVwbGljYXRpb24sIHRpbWVfd2FpdGluZywgY29udGFjdG8pIC0+IHdhaXRpbmdfdGltZXMNCndhaXRpbmdfdGltZXMNCmBgYA0KDQoNCmBgYHtyfQ0KIyBoaXN0b2dyYW1hIChuIHRvdSBhIHBlcmNlYmVyIHBxIMOpIHEgaMOhIHdhaXRpbmcgdGltZXMgbWFpb3JlcyBxdWUgNSkNCndhaXRpbmdfdGltZXMgJT4lIA0KICBmaWx0ZXIoY29udGFjdG8gPT0gInRlbGVmb25lIikgJT4lDQogIGdncGxvdChhZXMoeCA9IHRpbWVfd2FpdGluZykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpKyANCiAgeWxpbSgwLCAxMDAwKQ0KDQp3YWl0aW5nX3RpbWVzICU+JSANCiAgZmlsdGVyKGNvbnRhY3RvID09ICJhcHAiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gdGltZV93YWl0aW5nLzYwKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQoNCmBgYHtyfQ0KIyByZWd1bGFyIHdhaXRpbmcgdGltZSBzdGF0cyB3aXRob3V0IGdyYXBocw0Kd2FpdGluZ190aW1lcyAlPiUgDQogIGdyb3VwX2J5KGNvbnRhY3RvKSAlPiUgDQogIHN1bW1hcmlzZShtZWFuID0gbWVhbih0aW1lX3dhaXRpbmcpLCBzZCA9IHNkKHRpbWVfd2FpdGluZyksIG1lZGlhbiA9IG1lZGlhbih0aW1lX3dhaXRpbmcpKSANCndhaXRpbmdfdGltZXMgJT4lIA0KICBncm91cF9ieShjb250YWN0bykgJT4lIA0KICBzdW1tYXJpc2UocXVhbnRpbGVfMTAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuMSksIHF1YW50aWxlXzIwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjIpLCBxdWFudGlsZV8zMCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC4zKSwgcXVhbnRpbGVfNDAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuNCksIHF1YW50aWxlXzUwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjUpLCBxdWFudGlsZV82MCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC42KSwgcXVhbnRpbGVfNzAgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuNyksIHF1YW50aWxlXzgwID0gcXVhbnRpbGUodGltZV93YWl0aW5nLCAwLjgpLCBxdWFudGlsZV85MCA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC45KSwgcXVhbnRpbGVfOTUgPSBxdWFudGlsZSh0aW1lX3dhaXRpbmcsIDAuOTUpLCBxdWFudGlsZV85OSA9IHF1YW50aWxlKHRpbWVfd2FpdGluZywgMC45OSkpDQpgYGANCg0KDQpgYGB7cn0NCnJlc291cmNlcyAlPiUgcGxvdChtZXRyaWMgPSAidXRpbGl6YXRpb24iKQ0KcmVzb3VyY2VzICU+JSBwbG90KG1ldHJpYyA9ICJ1c2FnZSIsIHN0ZXBzID0gVCkNCmBgYA0KDQo=